Setup

library(ggplot2) # Load ggplot2
## Warning: package 'ggplot2' was built under R version 4.3.1
library(ggdark) # Load ggdark
## Warning: package 'ggdark' was built under R version 4.3.1
library(ggimage) # Load ggimage
## Warning: package 'ggimage' was built under R version 4.3.1
library(reshape) # Load reshape
## Warning: package 'reshape' was built under R version 4.3.1
library(ggridges) # Load ggridges
## Warning: package 'ggridges' was built under R version 4.3.1
library(ggrepel) # Load ggrepel
## Warning: package 'ggrepel' was built under R version 4.3.1
library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.3.1
## Warning: package 'tibble' was built under R version 4.3.1
## Warning: package 'tidyr' was built under R version 4.3.1
## Warning: package 'readr' was built under R version 4.3.1
## Warning: package 'purrr' was built under R version 4.3.1
## Warning: package 'dplyr' was built under R version 4.3.1
## Warning: package 'stringr' was built under R version 4.3.2
## Warning: package 'forcats' was built under R version 4.3.1
## Warning: package 'lubridate' was built under R version 4.3.1
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.2     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.0
## ✔ lubridate 1.9.2     ✔ tibble    3.2.1
## ✔ purrr     1.0.1     ✔ tidyr     1.3.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ tidyr::expand()    masks reshape::expand()
## ✖ dplyr::filter()    masks stats::filter()
## ✖ dplyr::lag()       masks stats::lag()
## ✖ dplyr::rename()    masks reshape::rename()
## ✖ lubridate::stamp() masks reshape::stamp()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(cfbfastR)
## Warning: package 'cfbfastR' was built under R version 4.3.1
# Load Functions:
source("sports_analytics_functions.r")
load("team_logos.rda")

The next few columns relate to the offensive performance of the school:

We then have the offensive statistics at a per play level:

The next set of columns relate to the defensive performance of the school:

Finally we have the defensive statistics at a per play level:

2015 Season

2015 Notre Dame Analysis

Setup

library(ggplot2) # Load ggplot2
library(ggdark) # Load ggdark
library(ggimage) # Load ggimage
library(reshape) # Load reshape
library(ggridges) # Load ggridges
library(ggrepel) # Load ggrepel
library(tidyverse)
library(cfbfastR)

Load 2015 Season

# Load data
cfb_2015 <- load_cfb_pbp(seasons = 2015)
# Convert to data frame
cfb_2015 <- as.data.frame(cfb_2015)

Play by Play

# Create summarized play type
s_play_type <- create_simplex_play_type(cfb_2015)
# Add summarized play type back to dataset
cfb_2015$playtype <- s_play_type
head(cfb_2015)
##   year week      id_play   game_id game_play_number half_play_number
## 1 2015    1 4.006038e+17 400603827                1                1
## 2 2015    1 4.006038e+17 400603827                2                2
## 3 2015    1 4.006038e+17 400603827                3                3
## 4 2015    1 4.006038e+17 400603827                4                4
## 5 2015    1 4.006038e+17 400603827                5                5
## 6 2015    1 4.006038e+17 400603827                6                6
##   drive_play_number pos_team def_pos_team pos_team_score def_pos_team_score
## 1                 1  Alabama    Wisconsin              0                  0
## 2                 2  Alabama    Wisconsin              0                  0
## 3                 3  Alabama    Wisconsin              0                  0
## 4                 4  Alabama    Wisconsin              0                  0
## 5                 5  Alabama    Wisconsin              0                  0
## 6                 6  Alabama    Wisconsin              0                  0
##   half period clock.minutes clock.seconds      play_type
## 1    1      1            15             0        Kickoff
## 2    1      1            14            45           Rush
## 3    1      1            14            15           Rush
## 4    1      1            13            55           Rush
## 5    1      1            13            15           Rush
## 6    1      1            12            40 Pass Reception
##                                                                            play_text
## 1 Andrew Endicott kickoff for 61 yds , Kenyan Drake return for 16 yds to the Alab 20
## 2                                         Derrick Henry run for 4 yds to the Alab 24
## 3                                         Derrick Henry run for 5 yds to the Alab 29
## 4                          Derrick Henry run for 3 yds to the Alab 32 for a 1ST down
## 5                                          Derrick Henry run for 1 yd to the Alab 33
## 6                  Jake Coker pass complete to Robert Foster for 1 yd to the Alab 34
##   down distance yards_to_goal yards_gained        EPA ep_before   ep_after
## 1    1       10            65           16 -0.4624372 0.8331681  0.3707309
## 2    1       10            80            4 -0.1516198 0.3707309  0.2191111
## 3    2        6            76            5  0.8101272 0.2191111  1.0292383
## 4    3        1            71            3  0.4974397 1.0292383  1.5266780
## 5    1       10            68            1 -0.7318541 1.5266780  0.7948239
## 6    2        9            67            1 -0.9457639 0.7948239 -0.1509401
##          wpa wp_before  wp_after def_wp_before def_wp_after penalty_detail
## 1 -0.0138547 0.4903198 0.4764651     0.5096802    0.5235349           <NA>
## 2  0.0028492 0.4764651 0.4793143     0.5235349    0.5206857           <NA>
## 3  0.0379041 0.4793143 0.5172184     0.5206857    0.4827816           <NA>
## 4  0.0297710 0.5172184 0.5469894     0.4827816    0.4530106           <NA>
## 5 -0.0220321 0.5469894 0.5249573     0.4530106    0.4750427           <NA>
## 6 -0.0333644 0.5249573 0.4915929     0.4750427    0.5084071           <NA>
##   yds_penalty penalty_1st_conv new_series firstD_by_kickoff firstD_by_poss
## 1          NA            FALSE          1                 1              0
## 2          NA            FALSE          0                 0              1
## 3          NA            FALSE          0                 0              0
## 4          NA            FALSE          0                 0              0
## 5          NA            FALSE          1                 0              0
## 6          NA            FALSE          0                 0              0
##   firstD_by_penalty firstD_by_yards    def_EPA   home_EPA   away_EPA
## 1                 0               0  0.4624372 -0.4624372  0.4624372
## 2                 0               0  0.1516198 -0.1516198  0.1516198
## 3                 0               0 -0.8101272  0.8101272 -0.8101272
## 4                 0               0 -0.4974397  0.4974397 -0.4974397
## 5                 0               1  0.7318541 -0.7318541  0.7318541
## 6                 0               0  0.9457639 -0.9457639  0.9457639
##   home_EPA_rush away_EPA_rush home_EPA_pass away_EPA_pass total_home_EPA
## 1            NA            NA            NA            NA    -0.46243724
## 2    -0.1516198            NA            NA            NA    -0.61405706
## 3     0.8101272            NA            NA            NA     0.19607015
## 4     0.4974397            NA            NA            NA     0.69350985
## 5    -0.7318541            NA            NA            NA    -0.03834422
## 6            NA            NA    -0.9457639            NA    -0.98410817
##   total_away_EPA total_home_EPA_rush total_away_EPA_rush total_home_EPA_pass
## 1     0.46243724           0.0000000                   0           0.0000000
## 2     0.61405706          -0.1516198                   0           0.0000000
## 3    -0.19607015           0.6585074                   0           0.0000000
## 4    -0.69350985           1.1559471                   0           0.0000000
## 5     0.03834422           0.4240930                   0           0.0000000
## 6     0.98410817           0.4240930                   0          -0.9457639
##   total_away_EPA_pass net_home_EPA net_away_EPA net_home_EPA_rush
## 1                   0  -0.92487447   0.92487447         0.0000000
## 2                   0  -1.22811412   1.22811412        -0.1516198
## 3                   0   0.39214030  -0.39214030         0.6585074
## 4                   0   1.38701969  -1.38701969         1.1559471
## 5                   0  -0.07668844   0.07668844         0.4240930
## 6                   0  -1.96821633   1.96821633         0.4240930
##   net_away_EPA_rush net_home_EPA_pass net_away_EPA_pass success epa_success
## 1         0.0000000         0.0000000         0.0000000       1           0
## 2         0.1516198         0.0000000         0.0000000       0           0
## 3        -0.6585074         0.0000000         0.0000000       1           1
## 4        -1.1559471         0.0000000         0.0000000       1           1
## 5        -0.4240930         0.0000000         0.0000000       0           0
## 6        -0.4240930        -0.9457639         0.9457639       0           0
##   rz_play scoring_opp middle_8 stuffed_run change_of_pos_team downs_turnover
## 1       0           0    FALSE           0                  0              0
## 2       0           0    FALSE           0                  0              0
## 3       0           0    FALSE           0                  0              0
## 4       0           0    FALSE           0                  0              0
## 5       0           0    FALSE           0                  0              0
## 6       0           0    FALSE           0                  0              0
##   turnover pos_score_diff_start pos_score_pts log_ydstogo ExpScoreDiff
## 1        0                    0             0    2.302585    0.8331681
## 2        0                    0             0    2.302585    0.3707309
## 3        0                    0             0    1.791759    0.2191111
## 4        0                    0             0    0.000000    1.0292383
## 5        0                    0             0    2.302585    1.5266780
## 6        0                    0             0    2.197225    0.7948239
##   ExpScoreDiff_Time_Ratio half_clock.minutes TimeSecsRem adj_TimeSecsRem
## 1            2.313713e-04                 30        1800            3600
## 2            1.033828e-04                 29        1785            3585
## 3            6.161728e-05                 29        1755            3555
## 4            2.910742e-04                 28        1735            3535
## 5            4.366928e-04                 28        1695            3495
## 6            2.296515e-04                 27        1660            3460
##   Goal_To_Go Under_two    home      away home_wp_before away_wp_before
## 1      FALSE     FALSE Alabama Wisconsin      0.4903198      0.5096802
## 2      FALSE     FALSE Alabama Wisconsin      0.4764651      0.5235349
## 3      FALSE     FALSE Alabama Wisconsin      0.4793143      0.5206857
## 4      FALSE     FALSE Alabama Wisconsin      0.5172184      0.4827816
## 5      FALSE     FALSE Alabama Wisconsin      0.5469894      0.4530106
## 6      FALSE     FALSE Alabama Wisconsin      0.5249573      0.4750427
##   home_wp_after away_wp_after end_of_half pos_team_receives_2H_kickoff
## 1     0.4764651     0.5235349           0                            0
## 2     0.4793143     0.5206857           0                            0
## 3     0.5172184     0.4827816           0                            0
## 4     0.5469894     0.4530106           0                            0
## 5     0.5249573     0.4750427           0                            0
## 6     0.4915929     0.5084071           0                            0
##   lead_pos_team lead_play_type lag_pos_team lag_play_type orig_play_type
## 1       Alabama           Rush      Alabama          <NA>        Kickoff
## 2       Alabama           Rush      Alabama       Kickoff           Rush
## 3       Alabama           Rush      Alabama          Rush           Rush
## 4       Alabama           Rush      Alabama          Rush           Rush
## 5       Alabama Pass Reception      Alabama          Rush           Rush
## 6       Alabama           Sack      Alabama          Rush Pass Reception
##   Under_three down_end distance_end log_ydstogo_end yards_to_goal_end
## 1       FALSE        1           10        2.302585                80
## 2       FALSE        2            6        1.791759                76
## 3       FALSE        3            1        0.000000                71
## 4       FALSE        1           10        2.302585                68
## 5       FALSE        2            9        2.197225                67
## 6       FALSE        3            8        2.079442                66
##   TimeSecsRem_end Goal_To_Go_end Under_two_end offense_score_play
## 1            1785          FALSE         FALSE                  0
## 2            1755          FALSE         FALSE                  0
## 3            1735          FALSE         FALSE                  0
## 4            1695          FALSE         FALSE                  0
## 5            1660          FALSE         FALSE                  0
## 6            1620          FALSE         FALSE                  0
##   defense_score_play         ppa yard_line scoring pos_team_timeouts_rem_before
## 1                  0          NA        65   FALSE                            3
## 2                  0 -0.08843213        20   FALSE                            3
## 3                  0  0.08232293        24   FALSE                            3
## 4                  0  0.87285627        29   FALSE                            3
## 5                  0 -0.76582560        32   FALSE                            3
## 6                  0 -0.49575594        33   FALSE                            3
##   def_pos_team_timeouts_rem_before pos_team_timeouts def_pos_team_timeouts
## 1                                3                 3                     3
## 2                                3                 3                     3
## 3                                3                 3                     3
## 4                                3                 3                     3
## 5                                3                 3                     3
## 6                                3                 3                     3
##   pos_score_diff pos_score_diff_start_end offense_play defense_play
## 1              0                        0    Wisconsin      Alabama
## 2              0                        0      Alabama    Wisconsin
## 3              0                        0      Alabama    Wisconsin
## 4              0                        0      Alabama    Wisconsin
## 5              0                        0      Alabama    Wisconsin
## 6              0                        0      Alabama    Wisconsin
##   offense_receives_2H_kickoff change_of_poss score_pts score_diff_start
## 1                           1              1         0                0
## 2                           0              0         0                0
## 3                           0              0         0                0
## 4                           0              0         0                0
## 5                           0              0         0                0
## 6                           0              0         0                0
##   score_diff offense_score defense_score offense_conference defense_conference
## 1          0             0             0            Big Ten                SEC
## 2          0             0             0                SEC            Big Ten
## 3          0             0             0                SEC            Big Ten
## 4          0             0             0                SEC            Big Ten
## 5          0             0             0                SEC            Big Ten
## 6          0             0             0                SEC            Big Ten
##   off_timeout_called def_timeout_called offense_timeouts defense_timeouts
## 1                  0                  0                3                3
## 2                  0                  0                3                3
## 3                  0                  0                3                3
## 4                  0                  0                3                3
## 5                  0                  0                3                3
## 6                  0                  0                3                3
##   off_timeouts_rem_before def_timeouts_rem_before rusher_player_name yds_rushed
## 1                       3                       3               <NA>         NA
## 2                       3                       3      Derrick Henry          4
## 3                       3                       3      Derrick Henry          5
## 4                       3                       3      Derrick Henry          3
## 5                       3                       3      Derrick Henry          1
## 6                       3                       3               <NA>         NA
##   passer_player_name receiver_player_name yds_receiving yds_sacked sack_players
## 1               <NA>                 <NA>            NA         NA         <NA>
## 2               <NA>                 <NA>            NA         NA         <NA>
## 3               <NA>                 <NA>            NA         NA         <NA>
## 4               <NA>                 <NA>            NA         NA         <NA>
## 5               <NA>                 <NA>            NA         NA         <NA>
## 6         Jake Coker        Robert Foster             1         NA         <NA>
##   sack_player_name sack_player_name2 pass_breakup_player_name
## 1             <NA>              <NA>                     <NA>
## 2             <NA>              <NA>                     <NA>
## 3             <NA>              <NA>                     <NA>
## 4             <NA>              <NA>                     <NA>
## 5             <NA>              <NA>                     <NA>
## 6             <NA>              <NA>                     <NA>
##   interception_player_name yds_int_return fumble_player_name
## 1                     <NA>             NA               <NA>
## 2                     <NA>             NA               <NA>
## 3                     <NA>             NA               <NA>
## 4                     <NA>             NA               <NA>
## 5                     <NA>             NA               <NA>
## 6                     <NA>             NA               <NA>
##   fumble_forced_player_name fumble_recovered_player_name yds_fumble_return
## 1                      <NA>                         <NA>                NA
## 2                      <NA>                         <NA>                NA
## 3                      <NA>                         <NA>                NA
## 4                      <NA>                         <NA>                NA
## 5                      <NA>                         <NA>                NA
## 6                      <NA>                         <NA>                NA
##   punter_player_name yds_punted punt_returner_player_name yds_punt_return
## 1               <NA>         NA                      <NA>              NA
## 2               <NA>         NA                      <NA>              NA
## 3               <NA>         NA                      <NA>              NA
## 4               <NA>         NA                      <NA>              NA
## 5               <NA>         NA                      <NA>              NA
## 6               <NA>         NA                      <NA>              NA
##   yds_punt_gained punt_block_player_name punt_block_return_player_name
## 1              NA                   <NA>                          <NA>
## 2              NA                   <NA>                          <NA>
## 3              NA                   <NA>                          <NA>
## 4              NA                   <NA>                          <NA>
## 5              NA                   <NA>                          <NA>
## 6              NA                   <NA>                          <NA>
##   fg_kicker_player_name yds_fg fg_block_player_name fg_return_player_name
## 1                  <NA>     NA                 <NA>                  <NA>
## 2                  <NA>     NA                 <NA>                  <NA>
## 3                  <NA>     NA                 <NA>                  <NA>
## 4                  <NA>     NA                 <NA>                  <NA>
## 5                  <NA>     NA                 <NA>                  <NA>
## 6                  <NA>     NA                 <NA>                  <NA>
##   kickoff_player_name yds_kickoff kickoff_returner_player_name
## 1     Andrew Endicott          61                 Kenyan Drake
## 2                <NA>          NA                         <NA>
## 3                <NA>          NA                         <NA>
## 4                <NA>          NA                         <NA>
## 5                <NA>          NA                         <NA>
## 6                <NA>          NA                         <NA>
##   yds_kickoff_return    new_id orig_drive_number drive_number
## 1                 16 101849902                 1            1
## 2                 NA 101855401                 1            1
## 3                 NA 101858401                 1            1
## 4                 NA 101864401                 1            1
## 5                 NA 101868401                 1            1
## 6                 NA 101875901                 1            1
##   drive_result_detailed new_drive_pts   drive_id drive_result
## 1                  Punt             0 4006038271         PUNT
## 2                  Punt             0 4006038271         PUNT
## 3                  Punt             0 4006038271         PUNT
## 4                  Punt             0 4006038271         PUNT
## 5                  Punt             0 4006038271         PUNT
## 6                  Punt             0 4006038271         PUNT
##   drive_start_yards_to_goal drive_end_yards_to_goal drive_yards drive_scoring
## 1                        80                      76           4             0
## 2                        80                      76           4             0
## 3                        80                      76           4             0
## 4                        80                      76           4             0
## 5                        80                      76           4             0
## 6                        80                      76           4             0
##   drive_pts drive_start_period drive_end_period drive_time_minutes_start
## 1         0                  1                1                       15
## 2         0                  1                1                       15
## 3         0                  1                1                       15
## 4         0                  1                1                       15
## 5         0                  1                1                       15
## 6         0                  1                1                       15
##   drive_time_seconds_start drive_time_minutes_end drive_time_seconds_end
## 1                        0                     11                     11
## 2                        0                     11                     11
## 3                        0                     11                     11
## 4                        0                     11                     11
## 5                        0                     11                     11
## 6                        0                     11                     11
##   drive_time_minutes_elapsed drive_time_seconds_elapsed drive_numbers
## 1                          3                         49             1
## 2                          3                         49             0
## 3                          3                         49             0
## 4                          3                         49             0
## 5                          3                         49             0
## 6                          3                         49             0
##   number_of_drives pts_scored drive_result_detailed_flag drive_result2
## 1                1          0                       <NA>          PUNT
## 2                1          0                       <NA>          PUNT
## 3                1          0                       <NA>          PUNT
## 4                1          0                       <NA>          PUNT
## 5                1          0                       <NA>          PUNT
## 6                1          0                       <NA>          PUNT
##   drive_num lag_drive_result_detailed lead_drive_result_detailed
## 1         1                      <NA>                       Punt
## 2         1                      Punt                       Punt
## 3         1                      Punt                       Punt
## 4         1                      Punt                       Punt
## 5         1                      Punt                       Punt
## 6         1                      Punt                       Punt
##   lag_new_drive_pts   id_drive rush rush_td pass pass_td completion
## 1                NA 4006038271    0       0    0       0          0
## 2                 0 4006038271    1       0    0       0          0
## 3                 0 4006038271    1       0    0       0          0
## 4                 0 4006038271    1       0    0       0          0
## 5                 0 4006038271    1       0    0       0          0
## 6                 0 4006038271    0       0    1       0          1
##   pass_attempt target sack_vec sack int int_td turnover_vec turnover_vec_lag
## 1            0      0        0    0   0      0            0               NA
## 2            0      0        0    0   0      0            0                0
## 3            0      0        0    0   0      0            0                0
## 4            0      0        0    0   0      0            0                0
## 5            0      0        0    0   0      0            0                0
## 6            1      1        0    0   0      0            0                0
##   turnover_indicator kickoff_play receives_2H_kickoff missing_yard_flag
## 1                  0            1                   0             FALSE
## 2                  0            0                   0             FALSE
## 3                  0            0                   0             FALSE
## 4                  0            0                   0             FALSE
## 5                  0            0                   0             FALSE
## 6                  0            0                   0             FALSE
##   scoring_play td_play touchdown safety fumble_vec kickoff_tb kickoff_onside
## 1            0       0         0      0          0          0              0
## 2            0       0         0      0          0          0              0
## 3            0       0         0      0          0          0              0
## 4            0       0         0      0          0          0              0
## 5            0       0         0      0          0          0              0
## 6            0       0         0      0          0          0              0
##   kickoff_oob kickoff_fair_catch kickoff_downed kickoff_safety kick_play punt
## 1           0                  0              0              0         1    0
## 2           0                  0              0              0         0    0
## 3           0                  0              0              0         0    0
## 4           0                  0              0              0         0    0
## 5           0                  0              0              0         0    0
## 6           0                  0              0              0         0    0
##   punt_play punt_tb punt_oob punt_fair_catch punt_downed punt_safety
## 1         0       0        0               0           0           0
## 2         0       0        0               0           0           0
## 3         0       0        0               0           0           0
## 4         0       0        0               0           0           0
## 5         0       0        0               0           0           0
## 6         0       0        0               0           0           0
##   punt_blocked penalty_safety fg_inds fg_made fg_make_prob No_Score_before
## 1            0              0       0   FALSE           NA     0.001775325
## 2            0              0       0   FALSE           NA     0.002281596
## 3            0              0       0   FALSE           NA     0.002551793
## 4            0              0       0   FALSE           NA     0.002566175
## 5            0              0       0   FALSE           NA     0.002786976
## 6            0              0       0   FALSE           NA     0.003397270
##   FG_before Opp_FG_before Opp_Safety_before Opp_TD_before Safety_before
## 1 0.1703466    0.08920160       0.001915283     0.2604038   0.002949699
## 2 0.1348319    0.12125747       0.004405305     0.3433359   0.003009137
## 3 0.1282595    0.12410270       0.003756808     0.3542328   0.003178192
## 4 0.1004425    0.11424110       0.002285337     0.3123559   0.003013381
## 5 0.1626506    0.09365869       0.002379197     0.2735665   0.003056112
## 6 0.1568627    0.10705790       0.002747635     0.3173046   0.003274382
##   TD_before No_Score_after  FG_after Opp_FG_after Opp_Safety_after Opp_TD_after
## 1 0.4734077    0.002281596 0.1348319   0.12125747      0.004405305    0.3433359
## 2 0.3908787    0.002551793 0.1282595   0.12410270      0.003756808    0.3542328
## 3 0.3839182    0.002566175 0.1004425   0.11424110      0.002285337    0.3123559
## 4 0.4650956    0.002786976 0.1626506   0.09365869      0.002379197    0.2735665
## 5 0.4619019    0.003397270 0.1568627   0.10705790      0.002747635    0.3173046
## 6 0.4093555    0.004085967 0.1371961   0.12685121      0.003444598    0.3753826
##   Safety_after  TD_after position_reception position_target position_completion
## 1  0.003009137 0.3908787               <NA>            <NA>                <NA>
## 2  0.003178192 0.3839182               <NA>            <NA>                <NA>
## 3  0.003013381 0.4650956               <NA>            <NA>                <NA>
## 4  0.003056112 0.4619019               <NA>            <NA>                <NA>
## 5  0.003274382 0.4093555               <NA>            <NA>                <NA>
## 6  0.003736811 0.3493027                 WR              WR                  QB
##   position_incompletion position_sack_taken position_sack
## 1                  <NA>                <NA>          <NA>
## 2                  <NA>                <NA>          <NA>
## 3                  <NA>                <NA>          <NA>
## 4                  <NA>                <NA>          <NA>
## 5                  <NA>                <NA>          <NA>
## 6                  <NA>                <NA>          <NA>
##   position_interception_thrown position_interception position_fumble
## 1                         <NA>                  <NA>            <NA>
## 2                         <NA>                  <NA>            <NA>
## 3                         <NA>                  <NA>            <NA>
## 4                         <NA>                  <NA>            <NA>
## 5                         <NA>                  <NA>            <NA>
## 6                         <NA>                  <NA>            <NA>
##   position_fumble_forced position_fumble_recovered position_pass_breakup
## 1                   <NA>                      <NA>                  <NA>
## 2                   <NA>                      <NA>                  <NA>
## 3                   <NA>                      <NA>                  <NA>
## 4                   <NA>                      <NA>                  <NA>
## 5                   <NA>                      <NA>                  <NA>
## 6                   <NA>                      <NA>                  <NA>
##   position_rush position_touchdown season  opponent team_score opponent_score
## 1          <NA>               <NA>     NA      <NA>         NA             NA
## 2            RB               <NA>   2015 Wisconsin          0              0
## 3            RB               <NA>   2015 Wisconsin          0              0
## 4            RB               <NA>   2015 Wisconsin          0              0
## 5            RB               <NA>   2015 Wisconsin          0              0
## 6          <NA>               <NA>   2015 Wisconsin          0              0
##   rush_player_id   rush_player rush_yds reception_player_id reception_player
## 1             NA          <NA>       NA                  NA             <NA>
## 2         546368 Derrick Henry        4                  NA             <NA>
## 3         546368 Derrick Henry        5                  NA             <NA>
## 4         546368 Derrick Henry        3                  NA             <NA>
## 5         546368 Derrick Henry        1                  NA             <NA>
## 6             NA          <NA>       NA              551293    Robert Foster
##   reception_yds completion_player_id completion_player completion_yds
## 1            NA                   NA              <NA>             NA
## 2            NA                   NA              <NA>             NA
## 3            NA                   NA              <NA>             NA
## 4            NA                   NA              <NA>             NA
## 5            NA                   NA              <NA>             NA
## 6             1               514124        Jake Coker              1
##   interception_player_id interception_player interception_stat
## 1                     NA                <NA>                NA
## 2                     NA                <NA>                NA
## 3                     NA                <NA>                NA
## 4                     NA                <NA>                NA
## 5                     NA                <NA>                NA
## 6                     NA                <NA>                NA
##   interception_thrown_player_id interception_thrown_player
## 1                            NA                       <NA>
## 2                            NA                       <NA>
## 3                            NA                       <NA>
## 4                            NA                       <NA>
## 5                            NA                       <NA>
## 6                            NA                       <NA>
##   interception_thrown_stat touchdown_player_id touchdown_player touchdown_stat
## 1                       NA                  NA             <NA>             NA
## 2                       NA                  NA             <NA>             NA
## 3                       NA                  NA             <NA>             NA
## 4                       NA                  NA             <NA>             NA
## 5                       NA                  NA             <NA>             NA
## 6                       NA                  NA             <NA>             NA
##   incompletion_player_id incompletion_player incompletion_stat target_player_id
## 1                     NA                <NA>                NA               NA
## 2                     NA                <NA>                NA               NA
## 3                     NA                <NA>                NA               NA
## 4                     NA                <NA>                NA               NA
## 5                     NA                <NA>                NA               NA
## 6                     NA                <NA>                NA               NA
##   target_player target_stat fumble_recovered_player_id fumble_recovered_player
## 1          <NA>          NA                         NA                    <NA>
## 2          <NA>          NA                         NA                    <NA>
## 3          <NA>          NA                         NA                    <NA>
## 4          <NA>          NA                         NA                    <NA>
## 5          <NA>          NA                         NA                    <NA>
## 6          <NA>          NA                         NA                    <NA>
##   fumble_recovered_stat fumble_forced_player_id fumble_forced_player
## 1                    NA                      NA                 <NA>
## 2                    NA                      NA                 <NA>
## 3                    NA                      NA                 <NA>
## 4                    NA                      NA                 <NA>
## 5                    NA                      NA                 <NA>
## 6                    NA                      NA                 <NA>
##   fumble_forced_stat fumble_player_id fumble_player fumble_stat sack_player_id
## 1                 NA               NA          <NA>          NA             NA
## 2                 NA               NA          <NA>          NA             NA
## 3                 NA               NA          <NA>          NA             NA
## 4                 NA               NA          <NA>          NA             NA
## 5                 NA               NA          <NA>          NA             NA
## 6                 NA               NA          <NA>          NA             NA
##   sack_player sack_stat sack_taken_player_id sack_taken_player sack_taken_stat
## 1        <NA>        NA                   NA              <NA>              NA
## 2        <NA>        NA                   NA              <NA>              NA
## 3        <NA>        NA                   NA              <NA>              NA
## 4        <NA>        NA                   NA              <NA>              NA
## 5        <NA>        NA                   NA              <NA>              NA
## 6        <NA>        NA                   NA              <NA>              NA
##   pass_breakup_player_id pass_breakup_player pass_breakup_stat play_id
## 1                     NA                <NA>                NA      NA
## 2                     NA                <NA>                NA      NA
## 3                     NA                <NA>                NA      NA
## 4                     NA                <NA>                NA      NA
## 5                     NA                <NA>                NA      NA
## 6                     NA                <NA>                NA      NA
##   penalty_flag penalty_declined penalty_no_play penalty_offset penalty_text
## 1        FALSE            FALSE           FALSE          FALSE        FALSE
## 2        FALSE            FALSE           FALSE          FALSE        FALSE
## 3        FALSE            FALSE           FALSE          FALSE        FALSE
## 4        FALSE            FALSE           FALSE          FALSE        FALSE
## 5        FALSE            FALSE           FALSE          FALSE        FALSE
## 6        FALSE            FALSE           FALSE          FALSE        FALSE
##   penalty_play_text row drive_event_number lag_play_type2 lag_play_type3
## 1              <NA>   1                  1           <NA>           <NA>
## 2              <NA>   2                  2           <NA>           <NA>
## 3              <NA>   3                  3        Kickoff           <NA>
## 4              <NA>   4                  4           Rush        Kickoff
## 5              <NA>   5                  5           Rush           Rush
## 6              <NA>   6                  6           Rush           Rush
##                                                                        lag_play_text
## 1                                                                               <NA>
## 2 Andrew Endicott kickoff for 61 yds , Kenyan Drake return for 16 yds to the Alab 20
## 3                                         Derrick Henry run for 4 yds to the Alab 24
## 4                                         Derrick Henry run for 5 yds to the Alab 29
## 5                          Derrick Henry run for 3 yds to the Alab 32 for a 1ST down
## 6                                          Derrick Henry run for 1 yd to the Alab 33
##                                                                       lag_play_text2
## 1                                                                               <NA>
## 2                                                                               <NA>
## 3 Andrew Endicott kickoff for 61 yds , Kenyan Drake return for 16 yds to the Alab 20
## 4                                         Derrick Henry run for 4 yds to the Alab 24
## 5                                         Derrick Henry run for 5 yds to the Alab 29
## 6                          Derrick Henry run for 3 yds to the Alab 32 for a 1ST down
##                                                            lead_play_text
## 1                              Derrick Henry run for 4 yds to the Alab 24
## 2                              Derrick Henry run for 5 yds to the Alab 29
## 3               Derrick Henry run for 3 yds to the Alab 32 for a 1ST down
## 4                               Derrick Henry run for 1 yd to the Alab 33
## 5       Jake Coker pass complete to Robert Foster for 1 yd to the Alab 34
## 6 Jake Coker sacked by Joe Schobert for a loss of 10 yards to the Alab 24
##   lag_first_by_penalty lag_first_by_penalty2 lag_first_by_yards
## 1                    0                    NA                  0
## 2                    0                    NA                  0
## 3                    0                     0                  0
## 4                    0                     0                  0
## 5                    0                     0                  1
## 6                    0                     0                  0
##   lag_first_by_yards2 first_by_penalty first_by_yards play_after_turnover
## 1                  NA                0              0                  NA
## 2                  NA                0              0                   0
## 3                   0                0              0                   0
## 4                   0                0              1                   0
## 5                   0                0              0                   0
## 6                   1                0              0                   0
##   lag_change_of_poss lag_change_of_pos_team lag_change_of_pos_team2
## 1                  0                      0                       0
## 2                  1                      0                       0
## 3                  0                      0                       0
## 4                  0                      0                       0
## 5                  0                      0                       0
## 6                  0                      0                       0
##   lag_kickoff_play lag_punt lag_punt2 lag_scoring_play lag_turnover_vec
## 1                0        0         0                0                0
## 2                1        0         0                0                0
## 3                0        0         0                0                0
## 4                0        0         0                0                0
## 5                0        0         0                0                0
## 6                0        0         0                0                0
##   lag_downs_turnover lag_defense_score_play playtype
## 1                  0                     NA  Kickoff
## 2                  0                      0     Rush
## 3                  0                      0     Rush
## 4                  0                      0     Rush
## 5                  0                      0     Rush
## 6                  0                      0     Pass

2015 Notre Dame Offensive Stats

nd_off_2015 <- cfb_2015[which(cfb_2015$pos_team == "Notre Dame" &
                     cfb_2015$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]


nd_off_2015_m <- melt(nd_off_2015[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2015 ND Offense EPA

# Create plot
ggplot(nd_off_2015_m, # Set dataset
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set geom density ridges
  theme_minimal() + # Set theme as minimal
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
       subtitle = "Notre Dame Offense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw()+ # Convert to dark theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.727

# Turn off dark mode
invert_geom_defaults()

2015 Notre Dame Defense

nd_def_2015 <- cfb_2015[which(cfb_2015$def_pos_team == "Notre Dame" &
                     cfb_2015$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

nd_def_2015_m <- melt(nd_def_2015[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2015 ND Defense EPA

# Create plot
ggplot(nd_def_2015_m, # Set dataset 
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set density to create distribution
  theme_minimal() + # Set plot theme
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
       subtitle = "Notre Dame Defense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw() + # Set theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.637

# Turn off dark mode
invert_geom_defaults()

By Field Position

y1_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$yards_to_goal >= 80,], 
                                team_logos = team_logos)

# Calculate statistics for 20 - 50
y2_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$yards_to_goal >= 50 &
                                                 cfb_2015$yards_to_goal < 80,], 
                                team_logos = team_logos)

# Calculate statistics for 50 - 79
y3_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$yards_to_goal >= 21 &
                                                 cfb_2015$yards_to_goal < 50,], 
                                team_logos = team_logos)
# Calculate statistics for 80 - 100
y4_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$yards_to_goal >= 0 &
                                                 cfb_2015$yards_to_goal < 20,], 
                                team_logos = team_logos)

2015 Notre Dame

# Set team as Notre Dame
team <- "Notre Dame"

# Extract Notre Dame statistics
nd_by_yards_2015 <- rbind.data.frame(y1_2015_stats[y1_2015_stats$school == team,],
                             y2_2015_stats[y2_2015_stats$school == team,],
                             y3_2015_stats[y3_2015_stats$school == team,],
                             y4_2015_stats[y4_2015_stats$school == team,])

# Create plot for offensive EPA
nd_epa_off_2015 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = team, # Set  Team
                 res_1 = nd_by_yards_2015, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "off") # Set type of plot to generate
## Warning: The `size` argument of `element_rect()` is deprecated as of ggplot2 3.4.0.
## ℹ Please use the `linewidth` argument instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
# Generate plot for offensive EPA
nd_epa_off_2015

# Create plot for offensive yards
nd_yards_2015 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2015, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "off") # Set type of plot to generate
# Generate plot
nd_yards_2015

2015 Defensive Stats

# Create plot for defensive EPA
nd_epa_def_2015 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2015, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
nd_epa_def_2015

# Yards
# Create plot for defensive yards
nd_yards_2015_def <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2015, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "def") # Set type of plot to generate
# Generate plot
nd_yards_2015_def

2015 Michigan

2015 Michigan Offensive Stats

mich_off_2015 <- cfb_2015[which(cfb_2015$pos_team == "Michigan" &
                     cfb_2015$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

mich_off_2015_m <- melt(mich_off_2015[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2015 Michigan Offense EPA

# Create plot
ggplot(mich_off_2015_m, # Set dataset
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set geom density ridges
  theme_minimal() + # Set theme as minimal
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
       subtitle = "Michigan Offense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw()+ # Convert to dark theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.789
## Warning: Removed 2 rows containing non-finite values (`stat_density_ridges()`).

# Turn off dark mode
invert_geom_defaults()

2015 Michigan Defense

mich_def_2015 <- cfb_2015[which(cfb_2015$def_pos_team == "Michigan" &
                     cfb_2015$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

mich_def_2015_m <- melt(mich_def_2015[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2015 Michigan Defense EPA

# Create plot
ggplot(mich_def_2015_m, # Set dataset 
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set density to create distribution
  theme_minimal() + # Set plot theme
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
       subtitle = "2015 Michigan Defense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw() + # Set theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.662
## Warning: Removed 10 rows containing non-finite values
## (`stat_density_ridges()`).

# Turn off dark mode
invert_geom_defaults()

By Field Position

2015 Michigan Offense

# Set team as Notre Dame
team <- "Michigan"

# Extract Notre Dame statistics
mich_by_yards_2015 <- rbind.data.frame(y1_2015_stats[y1_2015_stats$school == team,],
                             y2_2015_stats[y2_2015_stats$school == team,],
                             y3_2015_stats[y3_2015_stats$school == team,],
                             y4_2015_stats[y4_2015_stats$school == team,])

# Create plot for offensive EPA
mich_epa_off_2015 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2015, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
mich_epa_off_2015

# Create plot for offensive yards
mich_yards_2015 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2015, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "off") # Set type of plot to generate
# Generate plot
mich_yards_2015

2015 Michigan Defensive Stats

# Create plot for defensive EPA
mich_epa_def_2015 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2015, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
mich_epa_def_2015

# Yards
# Create plot for defensive yards
mich_yards_2015_def <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2015, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "def") # Set type of plot to generate
# Generate plot
mich_yards_2015_def

2015 Notre Dame vs Michigan Matchup

# Setting Data Up
# ND Pass and Rush Offense and Defense
nd_by_yards_2015$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
nd_by_yards_2015$zone_pass_num <- paste(nd_by_yards_2015$zone, ": ", nd_by_yards_2015$pass_n, sep = "")
nd_by_yards_2015$zone_rush_num <- paste(nd_by_yards_2015$zone, ": ", nd_by_yards_2015$rush_n, sep = "")
nd_by_yards_2015$zone_pass_num_def <- paste(nd_by_yards_2015$zone, ": ", nd_by_yards_2015$pass_n_def, sep = "")
nd_by_yards_2015$zone_rush_num_def <- paste(nd_by_yards_2015$zone, ": ", nd_by_yards_2015$rush_n_def, sep = "")

# Michigan Pass and Rush Offense and Defense
mich_by_yards_2015$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
mich_by_yards_2015$zone_pass_num <- paste(mich_by_yards_2015$zone, ": ", mich_by_yards_2015$pass_n, sep = "")
mich_by_yards_2015$zone_rush_num <- paste(mich_by_yards_2015$zone, ": ", mich_by_yards_2015$rush_n, sep = "")
mich_by_yards_2015$zone_pass_num_def <- paste(mich_by_yards_2015$zone, ": ", mich_by_yards_2015$pass_n_def, sep = "")
mich_by_yards_2015$zone_rush_num_def <- paste(mich_by_yards_2015$zone, ": ", mich_by_yards_2015$rush_n_def, sep = "")

# Create Different Combinations
nd_v_mich_pass_2015 <- cbind.data.frame(nd_by_yards_2015$zone, 
                                    ((nd_by_yards_2015$pass_wpa_per_play + mich_by_yards_2015$pass_wpa_per_play_def)/2),
                                    ((nd_by_yards_2015$pass_epa_per_play + mich_by_yards_2015$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2015) <- c("zone", "exp_wpa", "exp_epa")
nd_v_mich_rush_2015 <- cbind.data.frame(nd_by_yards_2015$zone, 
                                    ((nd_by_yards_2015$rush_wpa_per_play + mich_by_yards_2015$rush_wpa_per_play_def)/2),
                                    ((nd_by_yards_2015$rush_epa_per_play + mich_by_yards_2015$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2015) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_pass_2015 <- cbind.data.frame(mich_by_yards_2015$zone, 
                                    ((mich_by_yards_2015$pass_wpa_per_play + nd_by_yards_2015$pass_wpa_per_play_def)/2),
                                    ((mich_by_yards_2015$pass_epa_per_play + nd_by_yards_2015$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2015) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_rush_2015 <- cbind.data.frame(mich_by_yards_2015$zone, 
                                    ((mich_by_yards_2015$rush_wpa_per_play + nd_by_yards_2015$rush_wpa_per_play_def)/2),
                                    ((mich_by_yards_2015$rush_epa_per_play + nd_by_yards_2015$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2015) <- c("zone", "exp_wpa", "exp_epa")

WPA Graphs by Field Position

# ND Passing Offense vs Michigan Passing Defense
g_2015_1 <- ggplot(data = nd_v_mich_pass_2015, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2015_1

# ND Rushing Offense vs Michigan Rushing Defense
g_2015_2 <- ggplot(data = nd_v_mich_rush_2015, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2015_2

# Michigan Pass Offense vs Notre Dame Pass Defense
g_2015_3 <- ggplot(data = mich_v_nd_pass_2015, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2015_3

# Michigan Rush Offense vs Notre Dame Rush Defense
g_2015_4 <- ggplot(data = mich_v_nd_rush_2015, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2015_4

According to WPA, Notre Dame in 2015 would be about equally as successful either running or passing the ball over Michigan, while Michigan in 2015 would matchup well against the Irish in the passing attack. Best individual matchup would be Michigan’s passing offense against Notre Dame’s passing defense.

EPA Graphs by Field Position

# ND Passing Offense vs Michigan Passing Defense
g_2015_5 <- ggplot(data = nd_v_mich_pass_2015, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2015_5

# ND Rushing Offense vs Michigan Rushing Defense
g_2015_6 <- ggplot(data = nd_v_mich_rush_2015, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2015_6

# Michigan Pass Offense vs Notre Dame Pass Defense
g_2015_7 <- ggplot(data = mich_v_nd_pass_2015, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Expected Points Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2015_7

# Michigan Rush Offense vs Notre Dame Rush Defense
g_2015_8 <- ggplot(data = mich_v_nd_rush_2015, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2015_8

According to EPA, Notre Dame would be much wiser to run the ball on Michigan while Michigan should focus on passing the ball more against Notre Dame in 2015. Michigan’s passing attack against ND’s pass defense is the most favorable individual matchup.

Extracting Plays by Downs

z1_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$down == 1,], 
                                team_logos = team_logos)

# Calculate statistics for 2nd Down
z2_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$down == 2,], 
                                team_logos = team_logos)

# Calculate statistics for 3rd Down
z3_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$down == 3,], 
                                team_logos = team_logos)
# Calculate statistics for 4th Down
z4_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$down == 4,], 
                                team_logos = team_logos)

# Set team as Notre Dame
team <- "Notre Dame"

# Extract Notre Dame statistics
nd_by_down_2015 <- rbind.data.frame(z1_2015_stats[z1_2015_stats$school == team,],
                             z2_2015_stats[z2_2015_stats$school == team,],
                             z3_2015_stats[z3_2015_stats$school == team,],
                             z4_2015_stats[z4_2015_stats$school == team,])

# Set team as Michigan
team <- "Michigan"

# Extract Michigan statistics
mich_by_down_2015 <- rbind.data.frame(z1_2015_stats[z1_2015_stats$school == team,],
                             z2_2015_stats[z2_2015_stats$school == team,],
                             z3_2015_stats[z3_2015_stats$school == team,],
                             z4_2015_stats[z4_2015_stats$school == team,])

Setup Comparisons by Down

# ND Pass and Rush Offense and Defense
nd_by_down_2015$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
nd_by_down_2015$down_pass_num <- paste(nd_by_down_2015$down, ": ", nd_by_down_2015$pass_n, sep = "")
nd_by_down_2015$down_rush_num <- paste(nd_by_down_2015$down, ": ", nd_by_down_2015$rush_n, sep = "")
nd_by_down_2015$down_pass_num_def <- paste(nd_by_down_2015$down, ": ", nd_by_down_2015$pass_n_def, sep = "")
nd_by_down_2015$down_rush_num_def <- paste(nd_by_down_2015$down, ": ", nd_by_down_2015$rush_n_def, sep = "")

# Michigan Pass and Rush Offense and Defense
mich_by_down_2015$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
mich_by_down_2015$down_pass_num <- paste(mich_by_down_2015$down, ": ", mich_by_down_2015$pass_n, sep = "")
mich_by_down_2015$down_rush_num <- paste(mich_by_down_2015$down, ": ", mich_by_down_2015$rush_n, sep = "")
mich_by_down_2015$down_pass_num_def <- paste(mich_by_down_2015$down, ": ", mich_by_down_2015$pass_n_def, sep = "")
mich_by_down_2015$down_rush_num_def <- paste(mich_by_down_2015$down, ": ", mich_by_down_2015$rush_n_def, sep = "")

# Create Different Combinations
nd_v_mich_pass_2015_down <- cbind.data.frame(nd_by_down_2015$down, 
                                    ((nd_by_down_2015$pass_wpa_per_play + mich_by_down_2015$pass_wpa_per_play_def)/2),
                                    ((nd_by_down_2015$pass_epa_per_play + mich_by_down_2015$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2015_down) <- c("down", "exp_wpa", "exp_epa")
nd_v_mich_rush_2015_down <- cbind.data.frame(nd_by_down_2015$down, 
                                    ((nd_by_down_2015$rush_wpa_per_play + mich_by_down_2015$rush_wpa_per_play_def)/2),
                                    ((nd_by_down_2015$rush_epa_per_play + mich_by_down_2015$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2015_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_pass_2015_down <- cbind.data.frame(mich_by_down_2015$down, 
                                    ((mich_by_down_2015$pass_wpa_per_play + nd_by_down_2015$pass_wpa_per_play_def)/2),
                                    ((mich_by_down_2015$pass_epa_per_play + nd_by_down_2015$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2015_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_rush_2015_down <- cbind.data.frame(mich_by_down_2015$down, 
                                    ((mich_by_down_2015$rush_wpa_per_play + nd_by_down_2015$rush_wpa_per_play_def)/2),
                                    ((mich_by_down_2015$rush_epa_per_play + nd_by_down_2015$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2015_down) <- c("down", "exp_wpa", "exp_epa")

WPA Graphs by Down

# ND Passing Offense vs Michigan Passing Defense
d_2015_1 <- ggplot(data = nd_v_mich_pass_2015_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2015_1

# ND Rushing Offense vs Michigan Rushing Defense
d_2015_2 <- ggplot(data = nd_v_mich_rush_2015_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2015_2

# Michigan Pass Offense vs Notre Dame Pass Defense
d_2015_3 <- ggplot(data = mich_v_nd_pass_2015_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2015_3

# Michigan Rush Offense vs Notre Dame Rush Defense
d_2015_4 <- ggplot(data = mich_v_nd_rush_2015_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2015_4

Looking at it by down WPA reinforces that Notre Dame’s offense would be best served as a balanced attack, while Michigan would be much better suited to pass the ball against ND’s defense.

EPA by Down

# ND Passing Offense vs Michigan Passing Defense
d_2015_5 <- ggplot(data = nd_v_mich_pass_2015_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2015_5

# ND Rushing Offense vs Michigan Rushing Defense
d_2015_6 <- ggplot(data = nd_v_mich_rush_2015_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2015_6

# Michigan Pass Offense vs Notre Dame Pass Defense
d_2015_7 <- ggplot(data = mich_v_nd_pass_2015_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2015_7

# Michigan Rush Offense vs Notre Dame Rush Defense
d_2015_8 <- ggplot(data = mich_v_nd_rush_2015_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2015 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2015_8

EPA by down paints a scenario where Notre Dame is more likely to have success running the ball on Michigan as opposed to relying on their passing game, while Michigan’s passing attack once again garners a more favored outlook compared to their rushing attack.

2016 Season

Load 2016 Season

# Load data
cfb_2016 <- load_cfb_pbp(seasons = 2016)
# Convert to data frame
cfb_2016 <- as.data.frame(cfb_2016)

Play by Play

# Create summarized play type
s_play_type <- create_simplex_play_type(cfb_2016)
# Add summarized play type back to dataset
cfb_2016$playtype <- s_play_type

2016 Notre Dame Offensive Stats

nd_off_2016 <- cfb_2016[which(cfb_2016$pos_team == "Notre Dame" &
                     cfb_2016$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

nd_off_2016_m <- melt(nd_off_2016[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2016 ND Offense EPA

# Create plot
ggplot(nd_off_2016_m, # Set dataset
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set geom density ridges
  theme_minimal() + # Set theme as minimal
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
       subtitle = "2016 Notre Dame Offense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw()+ # Convert to dark theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.584
## Warning: Removed 4 rows containing non-finite values (`stat_density_ridges()`).

# Turn off dark mode
invert_geom_defaults()

2016 Notre Dame Defense

nd_def_2016 <- cfb_2016[which(cfb_2016$def_pos_team == "Notre Dame" &
                     cfb_2016$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

nd_def_2016_m <- melt(nd_def_2016[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2016 ND Defense EPA

# Create plot
ggplot(nd_def_2016_m, # Set dataset 
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set density to create distribution
  theme_minimal() + # Set plot theme
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
       subtitle = "2016 Notre Dame Defense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw() + # Set theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.675
## Warning: Removed 9 rows containing non-finite values (`stat_density_ridges()`).

# Turn off dark mode
invert_geom_defaults()

By Field Position

y1_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$yards_to_goal >= 80,], 
                                team_logos = team_logos)

# Calculate statistics for 20 - 50
y2_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$yards_to_goal >= 50 &
                                                 cfb_2016$yards_to_goal < 80,], 
                                team_logos = team_logos)

# Calculate statistics for 50 - 79
y3_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$yards_to_goal >= 21 &
                                                 cfb_2016$yards_to_goal < 50,], 
                                team_logos = team_logos)
# Calculate statistics for 80 - 100
y4_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$yards_to_goal >= 0 &
                                                 cfb_2016$yards_to_goal < 20,], 
                                team_logos = team_logos)

2016 Notre Dame Offense by Field Position

# Set team as Notre Dame
team <- "Notre Dame"

# Extract Notre Dame statistics
nd_by_yards_2016 <- rbind.data.frame(y1_2016_stats[y1_2016_stats$school == team,],
                             y2_2016_stats[y2_2016_stats$school == team,],
                             y3_2016_stats[y3_2016_stats$school == team,],
                             y4_2016_stats[y4_2016_stats$school == team,])

# Create plot for offensive EPA
nd_epa_off_2016 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2016, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
nd_epa_off_2016

# Create plot for offensive yards
nd_yards_2016 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2016, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "off") # Set type of plot to generate
# Generate plot
nd_yards_2016

2016 Notre Dame defense by field position

# Create plot for defensive EPA
nd_epa_def_2016 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2016, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
nd_epa_def_2016

# Yards
# Create plot for defensive yards
nd_yards_2016_def <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2016, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "def") # Set type of plot to generate
# Generate plot
nd_yards_2016_def

2016 Michigan

2016 Michigan Offensive Stats

mich_off_2016 <- cfb_2016[which(cfb_2016$pos_team == "Michigan" &
                     cfb_2016$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

mich_off_2016_m <- melt(mich_off_2016[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2016 Michigan Offense EPA

# Create plot
ggplot(mich_off_2016_m, # Set dataset
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set geom density ridges
  theme_minimal() + # Set theme as minimal
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
       subtitle = "2016 Michigan Offense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw()+ # Convert to dark theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.665
## Warning: Removed 10 rows containing non-finite values
## (`stat_density_ridges()`).

# Turn off dark mode
invert_geom_defaults()

2016 Michigan Defense

mich_def_2016 <- cfb_2016[which(cfb_2016$def_pos_team == "Michigan" &
                     cfb_2016$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

mich_def_2016_m <- melt(mich_def_2016[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2016 Michigan Defense EPA

# Create plot
ggplot(mich_def_2016_m, # Set dataset 
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set density to create distribution
  theme_minimal() + # Set plot theme
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
       subtitle = "2016 Michigan Defense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw() + # Set theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.584
## Warning: Removed 7 rows containing non-finite values (`stat_density_ridges()`).

# Turn off dark mode
invert_geom_defaults()

By Field Position

2016 Michigan Offense

# Set team as Notre Dame
team <- "Michigan"

# Extract Notre Dame statistics
mich_by_yards_2016 <- rbind.data.frame(y1_2016_stats[y1_2016_stats$school == team,],
                             y2_2016_stats[y2_2016_stats$school == team,],
                             y3_2016_stats[y3_2016_stats$school == team,],
                             y4_2016_stats[y4_2016_stats$school == team,])

# Create plot for offensive EPA
mich_epa_off_2016 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2016, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
mich_epa_off_2016

# Create plot for offensive yards
mich_yards_2016 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2016, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "off") # Set type of plot to generate
# Generate plot
mich_yards_2016

# Create plot for defensive EPA
mich_epa_def_2016 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2016, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
mich_epa_def_2016

# Yards
# Create plot for defensive yards
mich_yards_2016_def <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2016, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "def") # Set type of plot to generate
# Generate plot
mich_yards_2016_def

2016 Notre Dame vs Michigan Matchup

# Setting Data Up
# ND Pass and Rush Offense and Defense
nd_by_yards_2016$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
nd_by_yards_2016$zone_pass_num <- paste(nd_by_yards_2016$zone, ": ", nd_by_yards_2016$pass_n, sep = "")
nd_by_yards_2016$zone_rush_num <- paste(nd_by_yards_2016$zone, ": ", nd_by_yards_2016$rush_n, sep = "")
nd_by_yards_2016$zone_pass_num_def <- paste(nd_by_yards_2016$zone, ": ", nd_by_yards_2016$pass_n_def, sep = "")
nd_by_yards_2016$zone_rush_num_def <- paste(nd_by_yards_2016$zone, ": ", nd_by_yards_2016$rush_n_def, sep = "")

# Michigan Pass and Rush Offense and Defense
mich_by_yards_2016$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
mich_by_yards_2016$zone_pass_num <- paste(mich_by_yards_2016$zone, ": ", mich_by_yards_2016$pass_n, sep = "")
mich_by_yards_2016$zone_rush_num <- paste(mich_by_yards_2016$zone, ": ", mich_by_yards_2016$rush_n, sep = "")
mich_by_yards_2016$zone_pass_num_def <- paste(mich_by_yards_2016$zone, ": ", mich_by_yards_2016$pass_n_def, sep = "")
mich_by_yards_2016$zone_rush_num_def <- paste(mich_by_yards_2016$zone, ": ", mich_by_yards_2016$rush_n_def, sep = "")

# Create Different Combinations
nd_v_mich_pass_2016 <- cbind.data.frame(nd_by_yards_2016$zone, 
                                    ((nd_by_yards_2016$pass_wpa_per_play + mich_by_yards_2016$pass_wpa_per_play_def)/2),
                                    ((nd_by_yards_2016$pass_epa_per_play + mich_by_yards_2016$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2016) <- c("zone", "exp_wpa", "exp_epa")
nd_v_mich_rush_2016 <- cbind.data.frame(nd_by_yards_2016$zone, 
                                    ((nd_by_yards_2016$rush_wpa_per_play + mich_by_yards_2016$rush_wpa_per_play_def)/2),
                                    ((nd_by_yards_2016$rush_epa_per_play + mich_by_yards_2016$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2016) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_pass_2016 <- cbind.data.frame(mich_by_yards_2016$zone, 
                                    ((mich_by_yards_2016$pass_wpa_per_play + nd_by_yards_2016$pass_wpa_per_play_def)/2),
                                    ((mich_by_yards_2016$pass_epa_per_play + nd_by_yards_2016$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2016) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_rush_2016 <- cbind.data.frame(mich_by_yards_2016$zone, 
                                    ((mich_by_yards_2016$rush_wpa_per_play + nd_by_yards_2016$rush_wpa_per_play_def)/2),
                                    ((mich_by_yards_2016$rush_epa_per_play + nd_by_yards_2016$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2016) <- c("zone", "exp_wpa", "exp_epa")

WPA Graphs by Field Position

# ND Passing Offense vs Michigan Passing Defense
g_2016_1 <- ggplot(data = nd_v_mich_pass_2016, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2016_1

# ND Rushing Offense vs Michigan Rushing Defense
g_2016_2 <- ggplot(data = nd_v_mich_rush_2016, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2016_2

# Michigan Pass Offense vs Notre Dame Pass Defense
g_2016_3 <- ggplot(data = mich_v_nd_pass_2016, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2016_3

# Michigan Rush Offense vs Notre Dame Rush Defense
g_2016_4 <- ggplot(data = mich_v_nd_rush_2016, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2016_4

According to WPA, this paints a grim matchup for Notre Dame against Michigan. Notre Dame’s offense would need to be balanced, while Michigan could practically run whatever they want on offense because they sweep all matchups against Notre Dame’s defense.

EPA Graphs by Field Position

# ND Passing Offense vs Michigan Passing Defense
g_2016_5 <- ggplot(data = nd_v_mich_pass_2016, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2016_5

# ND Rushing Offense vs Michigan Rushing Defense
g_2016_6 <- ggplot(data = nd_v_mich_rush_2016, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2016_6

# Michigan Pass Offense vs Notre Dame Pass Defense
g_2016_7 <- ggplot(data = mich_v_nd_pass_2016, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Expected Points Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2016_7

# Michigan Rush Offense vs Notre Dame Rush Defense
g_2016_8 <- ggplot(data = mich_v_nd_rush_2016, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2016_8

EPA paints an even more grim matchup for the Irish, as the Irish offense wins less than half of the total matchups against Michigan’s defense, while the Wolverines win every conceivable matchup against ND. Both should prioritize throwing the ball as opposed to running it.

Extracting Plays by Downs

z1_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$down == 1,], 
                                team_logos = team_logos)

# Calculate statistics for 2nd Down
z2_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$down == 2,], 
                                team_logos = team_logos)

# Calculate statistics for 3rd Down
z3_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$down == 3,], 
                                team_logos = team_logos)
# Calculate statistics for 4th Down
z4_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$down == 4,], 
                                team_logos = team_logos)

# Set team as Notre Dame
team <- "Notre Dame"

# Extract Notre Dame statistics
nd_by_down_2016 <- rbind.data.frame(z1_2016_stats[z1_2016_stats$school == team,],
                             z2_2016_stats[z2_2016_stats$school == team,],
                             z3_2016_stats[z3_2016_stats$school == team,],
                             z4_2016_stats[z4_2016_stats$school == team,])

# Set team as Michigan
team <- "Michigan"

# Extract Michigan statistics
mich_by_down_2016 <- rbind.data.frame(z1_2016_stats[z1_2016_stats$school == team,],
                             z2_2016_stats[z2_2016_stats$school == team,],
                             z3_2016_stats[z3_2016_stats$school == team,],
                             z4_2016_stats[z4_2016_stats$school == team,])

Setup Comparisons by Down

# ND Pass and Rush Offense and Defense
nd_by_down_2016$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
nd_by_down_2016$down_pass_num <- paste(nd_by_down_2016$down, ": ", nd_by_down_2016$pass_n, sep = "")
nd_by_down_2016$down_rush_num <- paste(nd_by_down_2016$down, ": ", nd_by_down_2016$rush_n, sep = "")
nd_by_down_2016$down_pass_num_def <- paste(nd_by_down_2016$down, ": ", nd_by_down_2016$pass_n_def, sep = "")
nd_by_down_2016$down_rush_num_def <- paste(nd_by_down_2016$down, ": ", nd_by_down_2016$rush_n_def, sep = "")

# Michigan Pass and Rush Offense and Defense
mich_by_down_2016$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
mich_by_down_2016$down_pass_num <- paste(mich_by_down_2016$down, ": ", mich_by_down_2016$pass_n, sep = "")
mich_by_down_2016$down_rush_num <- paste(mich_by_down_2016$down, ": ", mich_by_down_2016$rush_n, sep = "")
mich_by_down_2016$down_pass_num_def <- paste(mich_by_down_2016$down, ": ", mich_by_down_2016$pass_n_def, sep = "")
mich_by_down_2016$down_rush_num_def <- paste(mich_by_down_2016$down, ": ", mich_by_down_2016$rush_n_def, sep = "")

# Create Different Combinations
nd_v_mich_pass_2016_down <- cbind.data.frame(nd_by_down_2016$down, 
                                    ((nd_by_down_2016$pass_wpa_per_play + mich_by_down_2016$pass_wpa_per_play_def)/2),
                                    ((nd_by_down_2016$pass_epa_per_play + mich_by_down_2016$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2016_down) <- c("down", "exp_wpa", "exp_epa")
nd_v_mich_rush_2016_down <- cbind.data.frame(nd_by_down_2016$down, 
                                    ((nd_by_down_2016$rush_wpa_per_play + mich_by_down_2016$rush_wpa_per_play_def)/2),
                                    ((nd_by_down_2016$rush_epa_per_play + mich_by_down_2016$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2016_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_pass_2016_down <- cbind.data.frame(mich_by_down_2016$down, 
                                    ((mich_by_down_2016$pass_wpa_per_play + nd_by_down_2016$pass_wpa_per_play_def)/2),
                                    ((mich_by_down_2016$pass_epa_per_play + nd_by_down_2016$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2016_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_rush_2016_down <- cbind.data.frame(mich_by_down_2016$down, 
                                    ((mich_by_down_2016$rush_wpa_per_play + nd_by_down_2016$rush_wpa_per_play_def)/2),
                                    ((mich_by_down_2016$rush_epa_per_play + nd_by_down_2016$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2016_down) <- c("down", "exp_wpa", "exp_epa")

WPA Graphs by Down

# ND Passing Offense vs Michigan Passing Defense
d_2016_1 <- ggplot(data = nd_v_mich_pass_2016_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2016_1

# ND Rushing Offense vs Michigan Rushing Defense
d_2016_2 <- ggplot(data = nd_v_mich_rush_2016_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2016_2

# Michigan Pass Offense vs Notre Dame Pass Defense
d_2016_3 <- ggplot(data = mich_v_nd_pass_2016_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2016_3

# Michigan Rush Offense vs Notre Dame Rush Defense
d_2016_4 <- ggplot(data = mich_v_nd_rush_2016_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2016_4

When analyzing WPA by down, ND should pass on first down, then run on other downs. Michigan still has a clear advantage over ND on offense.

EPA by Down

# ND Passing Offense vs Michigan Passing Defense
d_2016_5 <- ggplot(data = nd_v_mich_pass_2016_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2016_5

# ND Rushing Offense vs Michigan Rushing Defense
d_2016_6 <- ggplot(data = nd_v_mich_rush_2016_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2016_6

# Michigan Pass Offense vs Notre Dame Pass Defense
d_2016_7 <- ggplot(data = mich_v_nd_pass_2016_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2016_7

# Michigan Rush Offense vs Notre Dame Rush Defense
d_2016_8 <- ggplot(data = mich_v_nd_rush_2016_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2016 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2016_8

EPA by down paints a scenario where Notre dame should prioritize the ground attack, while Michigan should pass, although they still hold a sizable advantage over the Irish.

2017 Season

Load 2017 Season

# Load data
cfb_2017 <- load_cfb_pbp(seasons = 2017)
# Convert to data frame
cfb_2017 <- as.data.frame(cfb_2017)

Play by Play

# Create summarized play type
s_play_type <- create_simplex_play_type(cfb_2017)
# Add summarized play type back to dataset
cfb_2017$playtype <- s_play_type

2017 Notre Dame Offensive Stats

nd_off_2017 <- cfb_2017[which(cfb_2017$pos_team == "Notre Dame" &
                     cfb_2017$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

nd_off_2017_m <- melt(nd_off_2017[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2017 ND Offense EPA

# Create plot
ggplot(nd_off_2017_m, # Set dataset
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set geom density ridges
  theme_minimal() + # Set theme as minimal
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
       subtitle = "2017 Notre Dame Offense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw()+ # Convert to dark theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.843

# Turn off dark mode
invert_geom_defaults()

2017 Notre Dame Defense

nd_def_2017 <- cfb_2017[which(cfb_2017$def_pos_team == "Notre Dame" &
                     cfb_2017$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

nd_def_2017_m <- melt(nd_def_2017[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2017 ND Defense EPA

# Create plot
ggplot(nd_def_2017_m, # Set dataset 
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set density to create distribution
  theme_minimal() + # Set plot theme
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
       subtitle = "2017 Notre Dame Defense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw() + # Set theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.745

# Turn off dark mode
invert_geom_defaults()

By Field Position

y1_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$yards_to_goal >= 80,], 
                                team_logos = team_logos)

# Calculate statistics for 20 - 50
y2_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$yards_to_goal >= 50 &
                                                 cfb_2017$yards_to_goal < 80,], 
                                team_logos = team_logos)

# Calculate statistics for 50 - 79
y3_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$yards_to_goal >= 21 &
                                                 cfb_2017$yards_to_goal < 50,], 
                                team_logos = team_logos)
# Calculate statistics for 80 - 100
y4_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$yards_to_goal >= 0 &
                                                 cfb_2017$yards_to_goal < 20,], 
                                team_logos = team_logos)

2017 Notre Dame

# Set team as Notre Dame
team <- "Notre Dame"

# Extract Notre Dame statistics
nd_by_yards_2017 <- rbind.data.frame(y1_2017_stats[y1_2017_stats$school == team,],
                             y2_2017_stats[y2_2017_stats$school == team,],
                             y3_2017_stats[y3_2017_stats$school == team,],
                             y4_2017_stats[y4_2017_stats$school == team,])

# Create plot for offensive EPA
nd_epa_off_2017 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2017, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
nd_epa_off_2017

# Create plot for offensive yards
nd_yards_2017 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2017, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "off") # Set type of plot to generate
# Generate plot
nd_yards_2017

2017 Defensive Stats

# Create plot for defensive EPA
nd_epa_def_2017 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2017, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
nd_epa_def_2017

# Yards
# Create plot for defensive yards
nd_yards_2017_def <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2017, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "def") # Set type of plot to generate
# Generate plot
nd_yards_2017_def

2017 Michigan

2017 Michigan Offensive Stats

mich_off_2017 <- cfb_2017[which(cfb_2017$pos_team == "Michigan" &
                     cfb_2017$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

mich_off_2017_m <- melt(mich_off_2017[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2017 Michigan Offense EPA

# Create plot
ggplot(mich_off_2017_m, # Set dataset
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set geom density ridges
  theme_minimal() + # Set theme as minimal
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
       subtitle = "2017 Michigan Offense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw()+ # Convert to dark theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.499
## Warning: Removed 1 rows containing non-finite values (`stat_density_ridges()`).

# Turn off dark mode
invert_geom_defaults()

2017 Michigan Defense

mich_def_2017 <- cfb_2017[which(cfb_2017$def_pos_team == "Michigan" &
                     cfb_2017$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

mich_def_2017_m <- melt(mich_def_2017[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2017 Michigan Defense EPA

# Create plot
ggplot(mich_def_2017_m, # Set dataset 
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set density to create distribution
  theme_minimal() + # Set plot theme
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
       subtitle = "2017 Michigan Defense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw() + # Set theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.583
## Warning: Removed 5 rows containing non-finite values (`stat_density_ridges()`).

# Turn off dark mode
invert_geom_defaults()

By Field Position

2017 Michigan Offense

# Set team as Notre Dame
team <- "Michigan"

# Extract Notre Dame statistics
mich_by_yards_2017 <- rbind.data.frame(y1_2017_stats[y1_2017_stats$school == team,],
                             y2_2017_stats[y2_2017_stats$school == team,],
                             y3_2017_stats[y3_2017_stats$school == team,],
                             y4_2017_stats[y4_2017_stats$school == team,])

# Create plot for offensive EPA
mich_epa_off_2017 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2017, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
mich_epa_off_2017

# Create plot for offensive yards
mich_yards_2017 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2017, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "off") # Set type of plot to generate
# Generate plot
mich_yards_2017

2017 Michigan Defensive Stats

# Create plot for defensive EPA
mich_epa_def_2017 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2017, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
mich_epa_def_2017

# Yards
# Create plot for defensive yards
mich_yards_2017_def <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2017, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "def") # Set type of plot to generate
# Generate plot
mich_yards_2017_def

2017 Notre Dame vs Michigan Matchup

# Setting Data Up
# ND Pass and Rush Offense and Defense
nd_by_yards_2017$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
nd_by_yards_2017$zone_pass_num <- paste(nd_by_yards_2017$zone, ": ", nd_by_yards_2017$pass_n, sep = "")
nd_by_yards_2017$zone_rush_num <- paste(nd_by_yards_2017$zone, ": ", nd_by_yards_2017$rush_n, sep = "")
nd_by_yards_2017$zone_pass_num_def <- paste(nd_by_yards_2017$zone, ": ", nd_by_yards_2017$pass_n_def, sep = "")
nd_by_yards_2017$zone_rush_num_def <- paste(nd_by_yards_2017$zone, ": ", nd_by_yards_2017$rush_n_def, sep = "")

# Michigan Pass and Rush Offense and Defense
mich_by_yards_2017$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
mich_by_yards_2017$zone_pass_num <- paste(mich_by_yards_2017$zone, ": ", mich_by_yards_2017$pass_n, sep = "")
mich_by_yards_2017$zone_rush_num <- paste(mich_by_yards_2017$zone, ": ", mich_by_yards_2017$rush_n, sep = "")
mich_by_yards_2017$zone_pass_num_def <- paste(mich_by_yards_2017$zone, ": ", mich_by_yards_2017$pass_n_def, sep = "")
mich_by_yards_2017$zone_rush_num_def <- paste(mich_by_yards_2017$zone, ": ", mich_by_yards_2017$rush_n_def, sep = "")

# Create Different Combinations
nd_v_mich_pass_2017 <- cbind.data.frame(nd_by_yards_2017$zone, 
                                    ((nd_by_yards_2017$pass_wpa_per_play + mich_by_yards_2017$pass_wpa_per_play_def)/2),
                                    ((nd_by_yards_2017$pass_epa_per_play + mich_by_yards_2017$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2017) <- c("zone", "exp_wpa", "exp_epa")
nd_v_mich_rush_2017 <- cbind.data.frame(nd_by_yards_2017$zone, 
                                    ((nd_by_yards_2017$rush_wpa_per_play + mich_by_yards_2017$rush_wpa_per_play_def)/2),
                                    ((nd_by_yards_2017$rush_epa_per_play + mich_by_yards_2017$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2017) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_pass_2017 <- cbind.data.frame(mich_by_yards_2017$zone, 
                                    ((mich_by_yards_2017$pass_wpa_per_play + nd_by_yards_2017$pass_wpa_per_play_def)/2),
                                    ((mich_by_yards_2017$pass_epa_per_play + nd_by_yards_2017$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2017) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_rush_2017 <- cbind.data.frame(mich_by_yards_2017$zone, 
                                    ((mich_by_yards_2017$rush_wpa_per_play + nd_by_yards_2017$rush_wpa_per_play_def)/2),
                                    ((mich_by_yards_2017$rush_epa_per_play + nd_by_yards_2017$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2017) <- c("zone", "exp_wpa", "exp_epa")

WPA Graphs by Field Position

# ND Passing Offense vs Michigan Passing Defense
g_2017_1 <- ggplot(data = nd_v_mich_pass_2017, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2017_1

# ND Rushing Offense vs Michigan Rushing Defense
g_2017_2 <- ggplot(data = nd_v_mich_rush_2017, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2017_2

# Michigan Pass Offense vs Notre Dame Pass Defense
g_2017_3 <- ggplot(data = mich_v_nd_pass_2017, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2017_3

# Michigan Rush Offense vs Notre Dame Rush Defense
g_2017_4 <- ggplot(data = mich_v_nd_rush_2017, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2017_4

According to WPA by field position, this would be an incredibly balanced matchup. Generally, Notre Dame will have slightly more success passing the ball while Michigan will be slightly more successful at running the ball.

EPA Graphs by Field Position

# ND Passing Offense vs Michigan Passing Defense
g_2017_5 <- ggplot(data = nd_v_mich_pass_2017, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2017_5

# ND Rushing Offense vs Michigan Rushing Defense
g_2017_6 <- ggplot(data = nd_v_mich_rush_2017, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2017_6

# Michigan Pass Offense vs Notre Dame Pass Defense
g_2017_7 <- ggplot(data = mich_v_nd_pass_2017, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Expected Points Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2017_7

# Michigan Rush Offense vs Notre Dame Rush Defense
g_2017_8 <- ggplot(data = mich_v_nd_rush_2017, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2017_8

EPA by field position once again shows how close this matchup would be. This gives Notre Dame a more balanced approach on offense, but slightly favors the run. Michigan’s also advocates a balanced attack, but slightly favors the pass here.

Extracting Plays by Downs

z1_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$down == 1,], 
                                team_logos = team_logos)

# Calculate statistics for 2nd Down
z2_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$down == 2,], 
                                team_logos = team_logos)

# Calculate statistics for 3rd Down
z3_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$down == 3,], 
                                team_logos = team_logos)
# Calculate statistics for 4th Down
z4_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$down == 4,], 
                                team_logos = team_logos)

# Set team as Notre Dame
team <- "Notre Dame"

# Extract Notre Dame statistics
nd_by_down_2017 <- rbind.data.frame(z1_2017_stats[z1_2017_stats$school == team,],
                             z2_2017_stats[z2_2017_stats$school == team,],
                             z3_2017_stats[z3_2017_stats$school == team,],
                             z4_2017_stats[z4_2017_stats$school == team,])

# Set team as Michigan
team <- "Michigan"

# Extract Michigan statistics
mich_by_down_2017 <- rbind.data.frame(z1_2017_stats[z1_2017_stats$school == team,],
                             z2_2017_stats[z2_2017_stats$school == team,],
                             z3_2017_stats[z3_2017_stats$school == team,],
                             z4_2017_stats[z4_2017_stats$school == team,])

Setup Comparisons by Down

# ND Pass and Rush Offense and Defense
nd_by_down_2017$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
nd_by_down_2017$down_pass_num <- paste(nd_by_down_2017$down, ": ", nd_by_down_2017$pass_n, sep = "")
nd_by_down_2017$down_rush_num <- paste(nd_by_down_2017$down, ": ", nd_by_down_2017$rush_n, sep = "")
nd_by_down_2017$down_pass_num_def <- paste(nd_by_down_2017$down, ": ", nd_by_down_2017$pass_n_def, sep = "")
nd_by_down_2017$down_rush_num_def <- paste(nd_by_down_2017$down, ": ", nd_by_down_2017$rush_n_def, sep = "")

# Michigan Pass and Rush Offense and Defense
mich_by_down_2017$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
mich_by_down_2017$down_pass_num <- paste(mich_by_down_2017$down, ": ", mich_by_down_2017$pass_n, sep = "")
mich_by_down_2017$down_rush_num <- paste(mich_by_down_2017$down, ": ", mich_by_down_2017$rush_n, sep = "")
mich_by_down_2017$down_pass_num_def <- paste(mich_by_down_2017$down, ": ", mich_by_down_2017$pass_n_def, sep = "")
mich_by_down_2017$down_rush_num_def <- paste(mich_by_down_2017$down, ": ", mich_by_down_2017$rush_n_def, sep = "")

# Create Different Combinations
nd_v_mich_pass_2017_down <- cbind.data.frame(nd_by_down_2017$down, 
                                    ((nd_by_down_2017$pass_wpa_per_play + mich_by_down_2017$pass_wpa_per_play_def)/2),
                                    ((nd_by_down_2017$pass_epa_per_play + mich_by_down_2017$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2017_down) <- c("down", "exp_wpa", "exp_epa")
nd_v_mich_rush_2017_down <- cbind.data.frame(nd_by_down_2017$down, 
                                    ((nd_by_down_2017$rush_wpa_per_play + mich_by_down_2017$rush_wpa_per_play_def)/2),
                                    ((nd_by_down_2017$rush_epa_per_play + mich_by_down_2017$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2017_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_pass_2017_down <- cbind.data.frame(mich_by_down_2017$down, 
                                    ((mich_by_down_2017$pass_wpa_per_play + nd_by_down_2017$pass_wpa_per_play_def)/2),
                                    ((mich_by_down_2017$pass_epa_per_play + nd_by_down_2017$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2017_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_rush_2017_down <- cbind.data.frame(mich_by_down_2017$down, 
                                    ((mich_by_down_2017$rush_wpa_per_play + nd_by_down_2017$rush_wpa_per_play_def)/2),
                                    ((mich_by_down_2017$rush_epa_per_play + nd_by_down_2017$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2017_down) <- c("down", "exp_wpa", "exp_epa")

WPA Graphs by Down

# ND Passing Offense vs Michigan Passing Defense
d_2017_1 <- ggplot(data = nd_v_mich_pass_2017_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2017_1

# ND Rushing Offense vs Michigan Rushing Defense
d_2017_2 <- ggplot(data = nd_v_mich_rush_2017_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2017_2

# Michigan Pass Offense vs Notre Dame Pass Defense
d_2017_3 <- ggplot(data = mich_v_nd_pass_2017_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2017_3

# Michigan Rush Offense vs Notre Dame Rush Defense
d_2017_4 <- ggplot(data = mich_v_nd_rush_2017_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2017_4

These series of graphs show that Notre Dame is likely going to be more successful on the ground, while Michigan will be much better suited to pass the ball except on 3rd down.

EPA by Down

# ND Passing Offense vs Michigan Passing Defense
d_2017_5 <- ggplot(data = nd_v_mich_pass_2017_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2017_5

# ND Rushing Offense vs Michigan Rushing Defense
d_2017_6 <- ggplot(data = nd_v_mich_rush_2017_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2017_6

# Michigan Pass Offense vs Notre Dame Pass Defense
d_2017_7 <- ggplot(data = mich_v_nd_pass_2017_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2017_7

# Michigan Rush Offense vs Notre Dame Rush Defense
d_2017_8 <- ggplot(data = mich_v_nd_rush_2017_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2017 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2017_8

This has similar conclusions, Notre Dame is likely to be more successful with the rushing attack, Michigan will be more successful throwing the ball. Michigan’s passing attack has the best matchup.

2020 Season

Load 2020 Season

# Load data
cfb_2020 <- load_cfb_pbp(seasons = 2020)
# Convert to data frame
cfb_2020 <- as.data.frame(cfb_2020)

Play by Play

# Create summarized play type
s_play_type <- create_simplex_play_type(cfb_2020)
# Add summarized play type back to dataset
cfb_2020$playtype <- s_play_type

2020 Notre Dame Offensive Stats

nd_off_2020 <- cfb_2020[which(cfb_2020$pos_team == "Notre Dame" &
                     cfb_2020$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

nd_off_2020_m <- melt(nd_off_2020[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2020 ND Offense EPA

# Create plot
ggplot(nd_off_2020_m, # Set dataset
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set geom density ridges
  theme_minimal() + # Set theme as minimal
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
       subtitle = "2020 Notre Dame Offense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw()+ # Convert to dark theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.499
## Warning: Removed 11 rows containing non-finite values
## (`stat_density_ridges()`).

# Turn off dark mode
invert_geom_defaults()

2020 Notre Dame Defense

nd_def_2020 <- cfb_2020[which(cfb_2020$def_pos_team == "Notre Dame" &
                     cfb_2020$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

nd_def_2020_m <- melt(nd_def_2020[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2020 ND Defense EPA

# Create plot
ggplot(nd_def_2020_m, # Set dataset 
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set density to create distribution
  theme_minimal() + # Set plot theme
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
       subtitle = "2020 Notre Dame Defense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw() + # Set theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.767
## Warning: Removed 5 rows containing non-finite values (`stat_density_ridges()`).

# Turn off dark mode
invert_geom_defaults()

By Field Position

y1_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$yards_to_goal >= 80,], 
                                team_logos = team_logos)

# Calculate statistics for 20 - 50
y2_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$yards_to_goal >= 50 &
                                                 cfb_2020$yards_to_goal < 80,], 
                                team_logos = team_logos)

# Calculate statistics for 50 - 79
y3_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$yards_to_goal >= 21 &
                                                 cfb_2020$yards_to_goal < 50,], 
                                team_logos = team_logos)
# Calculate statistics for 80 - 100
y4_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$yards_to_goal >= 0 &
                                                 cfb_2020$yards_to_goal < 20,], 
                                team_logos = team_logos)

2020 Notre Dame Offense

# Set team as Notre Dame
team <- "Notre Dame"

# Extract Notre Dame statistics
nd_by_yards_2020 <- rbind.data.frame(y1_2020_stats[y1_2020_stats$school == team,],
                             y2_2020_stats[y2_2020_stats$school == team,],
                             y3_2020_stats[y3_2020_stats$school == team,],
                             y4_2020_stats[y4_2020_stats$school == team,])

# Create plot for offensive EPA
nd_epa_off_2020 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2020, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
nd_epa_off_2020

# Create plot for offensive yards
nd_yards_2020 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2020, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "off") # Set type of plot to generate
# Generate plot
nd_yards_2020

2020 Defensive Stats

# Create plot for defensive EPA
nd_epa_def_2020 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2020, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
nd_epa_def_2020

# Yards
# Create plot for defensive yards
nd_yards_2020_def <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2020, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "def") # Set type of plot to generate
# Generate plot
nd_yards_2020_def

2020 Michigan

2020 Michigan Offensive Stats

mich_off_2020 <- cfb_2020[which(cfb_2020$pos_team == "Michigan" &
                     cfb_2020$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

mich_off_2020_m <- melt(mich_off_2020[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2020 Michigan Offense EPA

# Create plot
ggplot(mich_off_2020_m, # Set dataset
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set geom density ridges
  theme_minimal() + # Set theme as minimal
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
       subtitle = "2020 Michigan Offense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw()+ # Convert to dark theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.947
## Warning: Removed 15 rows containing non-finite values
## (`stat_density_ridges()`).

# Turn off dark mode
invert_geom_defaults()

2020 Michigan Defense

mich_def_2020 <- cfb_2020[which(cfb_2020$def_pos_team == "Michigan" &
                     cfb_2020$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

mich_def_2020_m <- melt(mich_def_2020[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2020 Michigan Defense EPA

# Create plot
ggplot(mich_def_2020_m, # Set dataset 
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set density to create distribution
  theme_minimal() + # Set plot theme
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
       subtitle = "2020 Michigan Defense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw() + # Set theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.795
## Warning: Removed 7 rows containing non-finite values (`stat_density_ridges()`).

# Turn off dark mode
invert_geom_defaults()

By Field Position

2020 Michigan Offense

# Set team as Notre Dame
team <- "Michigan"

# Extract Notre Dame statistics
mich_by_yards_2020 <- rbind.data.frame(y1_2020_stats[y1_2020_stats$school == team,],
                             y2_2020_stats[y2_2020_stats$school == team,],
                             y3_2020_stats[y3_2020_stats$school == team,],
                             y4_2020_stats[y4_2020_stats$school == team,])

# Create plot for offensive EPA
mich_epa_off_2020 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2020, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
mich_epa_off_2020

# Create plot for offensive yards
mich_yards_2020 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2020, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "off") # Set type of plot to generate
# Generate plot
mich_yards_2020

2020 Michigan Defensive Stats

# Create plot for defensive EPA
mich_epa_def_2020 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2020, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
mich_epa_def_2020

# Yards
# Create plot for defensive yards
mich_yards_2020_def <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2020, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "def") # Set type of plot to generate
# Generate plot
mich_yards_2020_def

2020 Notre Dame vs Michigan Matchup

# Setting Data Up
# ND Pass and Rush Offense and Defense
nd_by_yards_2020$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
nd_by_yards_2020$zone_pass_num <- paste(nd_by_yards_2020$zone, ": ", nd_by_yards_2020$pass_n, sep = "")
nd_by_yards_2020$zone_rush_num <- paste(nd_by_yards_2020$zone, ": ", nd_by_yards_2020$rush_n, sep = "")
nd_by_yards_2020$zone_pass_num_def <- paste(nd_by_yards_2020$zone, ": ", nd_by_yards_2020$pass_n_def, sep = "")
nd_by_yards_2020$zone_rush_num_def <- paste(nd_by_yards_2020$zone, ": ", nd_by_yards_2020$rush_n_def, sep = "")

# Michigan Pass and Rush Offense and Defense
mich_by_yards_2020$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
mich_by_yards_2020$zone_pass_num <- paste(mich_by_yards_2020$zone, ": ", mich_by_yards_2020$pass_n, sep = "")
mich_by_yards_2020$zone_rush_num <- paste(mich_by_yards_2020$zone, ": ", mich_by_yards_2020$rush_n, sep = "")
mich_by_yards_2020$zone_pass_num_def <- paste(mich_by_yards_2020$zone, ": ", mich_by_yards_2020$pass_n_def, sep = "")
mich_by_yards_2020$zone_rush_num_def <- paste(mich_by_yards_2020$zone, ": ", mich_by_yards_2020$rush_n_def, sep = "")

# Create Different Combinations
nd_v_mich_pass_2020 <- cbind.data.frame(nd_by_yards_2020$zone, 
                                    ((nd_by_yards_2020$pass_wpa_per_play + mich_by_yards_2020$pass_wpa_per_play_def)/2),
                                    ((nd_by_yards_2020$pass_epa_per_play + mich_by_yards_2020$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2020) <- c("zone", "exp_wpa", "exp_epa")
nd_v_mich_rush_2020 <- cbind.data.frame(nd_by_yards_2020$zone, 
                                    ((nd_by_yards_2020$rush_wpa_per_play + mich_by_yards_2020$rush_wpa_per_play_def)/2),
                                    ((nd_by_yards_2020$rush_epa_per_play + mich_by_yards_2020$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2020) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_pass_2020 <- cbind.data.frame(mich_by_yards_2020$zone, 
                                    ((mich_by_yards_2020$pass_wpa_per_play + nd_by_yards_2020$pass_wpa_per_play_def)/2),
                                    ((mich_by_yards_2020$pass_epa_per_play + nd_by_yards_2020$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2020) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_rush_2020 <- cbind.data.frame(mich_by_yards_2020$zone, 
                                    ((mich_by_yards_2020$rush_wpa_per_play + nd_by_yards_2020$rush_wpa_per_play_def)/2),
                                    ((mich_by_yards_2020$rush_epa_per_play + nd_by_yards_2020$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2020) <- c("zone", "exp_wpa", "exp_epa")

WPA Graphs by Field Position

# ND Passing Offense vs Michigan Passing Defense
g_2020_1 <- ggplot(data = nd_v_mich_pass_2020, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2020_1

# ND Rushing Offense vs Michigan Rushing Defense
g_2020_2 <- ggplot(data = nd_v_mich_rush_2020, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2020_2

# Michigan Pass Offense vs Notre Dame Pass Defense
g_2020_3 <- ggplot(data = mich_v_nd_pass_2020, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2020_3

# Michigan Rush Offense vs Notre Dame Rush Defense
g_2020_4 <- ggplot(data = mich_v_nd_rush_2020, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2020_4

According to WPA by field position, Notre Dame’s offense generally has far more favorable matchups against Michigan than the other way around. ND’s passing offense against Michigan’s passing defense serves as the best individual matchup.

EPA Graphs by Field Position

# ND Passing Offense vs Michigan Passing Defense
g_2020_5 <- ggplot(data = nd_v_mich_pass_2020, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2020_5

# ND Rushing Offense vs Michigan Rushing Defense
g_2020_6 <- ggplot(data = nd_v_mich_rush_2020, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2020_6

# Michigan Pass Offense vs Notre Dame Pass Defense
g_2020_7 <- ggplot(data = mich_v_nd_pass_2020, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Expected Points Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2020_7

# Michigan Rush Offense vs Notre Dame Rush Defense
g_2020_8 <- ggplot(data = mich_v_nd_rush_2020, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2020_8

Similar situation to previous graph, Notre Dame would fare incredibly well throwing the ball and fairly well running the ball, while Michigan would throw the ball okay but struggle running the ball.

Extracting Plays by Downs

z1_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$down == 1,], 
                                team_logos = team_logos)

# Calculate statistics for 2nd Down
z2_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$down == 2,], 
                                team_logos = team_logos)

# Calculate statistics for 3rd Down
z3_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$down == 3,], 
                                team_logos = team_logos)
# Calculate statistics for 4th Down
z4_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$down == 4,], 
                                team_logos = team_logos)

# Set team as Notre Dame
team <- "Notre Dame"

# Extract Notre Dame statistics
nd_by_down_2020 <- rbind.data.frame(z1_2020_stats[z1_2020_stats$school == team,],
                             z2_2020_stats[z2_2020_stats$school == team,],
                             z3_2020_stats[z3_2020_stats$school == team,],
                             z4_2020_stats[z4_2020_stats$school == team,])

# Set team as Michigan
team <- "Michigan"

# Extract Michigan statistics
mich_by_down_2020 <- rbind.data.frame(z1_2020_stats[z1_2020_stats$school == team,],
                             z2_2020_stats[z2_2020_stats$school == team,],
                             z3_2020_stats[z3_2020_stats$school == team,],
                             z4_2020_stats[z4_2020_stats$school == team,])

Setup Comparisons by Down

# ND Pass and Rush Offense and Defense
nd_by_down_2020$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
nd_by_down_2020$down_pass_num <- paste(nd_by_down_2020$down, ": ", nd_by_down_2020$pass_n, sep = "")
nd_by_down_2020$down_rush_num <- paste(nd_by_down_2020$down, ": ", nd_by_down_2020$rush_n, sep = "")
nd_by_down_2020$down_pass_num_def <- paste(nd_by_down_2020$down, ": ", nd_by_down_2020$pass_n_def, sep = "")
nd_by_down_2020$down_rush_num_def <- paste(nd_by_down_2020$down, ": ", nd_by_down_2020$rush_n_def, sep = "")

# Michigan Pass and Rush Offense and Defense
mich_by_down_2020$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
mich_by_down_2020$down_pass_num <- paste(mich_by_down_2020$down, ": ", mich_by_down_2020$pass_n, sep = "")
mich_by_down_2020$down_rush_num <- paste(mich_by_down_2020$down, ": ", mich_by_down_2020$rush_n, sep = "")
mich_by_down_2020$down_pass_num_def <- paste(mich_by_down_2020$down, ": ", mich_by_down_2020$pass_n_def, sep = "")
mich_by_down_2020$down_rush_num_def <- paste(mich_by_down_2020$down, ": ", mich_by_down_2020$rush_n_def, sep = "")

# Create Different Combinations
nd_v_mich_pass_2020_down <- cbind.data.frame(nd_by_down_2020$down, 
                                    ((nd_by_down_2020$pass_wpa_per_play + mich_by_down_2020$pass_wpa_per_play_def)/2),
                                    ((nd_by_down_2020$pass_epa_per_play + mich_by_down_2020$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2020_down) <- c("down", "exp_wpa", "exp_epa")
nd_v_mich_rush_2020_down <- cbind.data.frame(nd_by_down_2020$down, 
                                    ((nd_by_down_2020$rush_wpa_per_play + mich_by_down_2020$rush_wpa_per_play_def)/2),
                                    ((nd_by_down_2020$rush_epa_per_play + mich_by_down_2020$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2020_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_pass_2020_down <- cbind.data.frame(mich_by_down_2020$down, 
                                    ((mich_by_down_2020$pass_wpa_per_play + nd_by_down_2020$pass_wpa_per_play_def)/2),
                                    ((mich_by_down_2020$pass_epa_per_play + nd_by_down_2020$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2020_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_rush_2020_down <- cbind.data.frame(mich_by_down_2020$down, 
                                    ((mich_by_down_2020$rush_wpa_per_play + nd_by_down_2020$rush_wpa_per_play_def)/2),
                                    ((mich_by_down_2020$rush_epa_per_play + nd_by_down_2020$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2020_down) <- c("down", "exp_wpa", "exp_epa")

WPA Graphs by Down

# ND Passing Offense vs Michigan Passing Defense
d_2020_1 <- ggplot(data = nd_v_mich_pass_2020_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2020_1

# ND Rushing Offense vs Michigan Rushing Defense
d_2020_2 <- ggplot(data = nd_v_mich_rush_2020_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2020_2

# Michigan Pass Offense vs Notre Dame Pass Defense
d_2020_3 <- ggplot(data = mich_v_nd_pass_2020_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2020_3

# Michigan Rush Offense vs Notre Dame Rush Defense
d_2020_4 <- ggplot(data = mich_v_nd_rush_2020_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2020_4

Notre Dame’s passing offense is the best individual matchup here. Both Michigan and ND would pass it fine, but Notre Dame wpuld dominate on the ground.

EPA by Down

# ND Passing Offense vs Michigan Passing Defense
d_2020_5 <- ggplot(data = nd_v_mich_pass_2020_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2020_5

# ND Rushing Offense vs Michigan Rushing Defense
d_2020_6 <- ggplot(data = nd_v_mich_rush_2020_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2020_6

# Michigan Pass Offense vs Notre Dame Pass Defense
d_2020_7 <- ggplot(data = mich_v_nd_pass_2020_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2020_7

# Michigan Rush Offense vs Notre Dame Rush Defense
d_2020_8 <- ggplot(data = mich_v_nd_rush_2020_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2020 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2020_8

Similar situation, Notre Dame passing offense and favorable rushing offense beats Michigan’s passing offense and unfavorable rushing offense.

2021 Season

Load 2021 Season

# Load data
cfb_2021 <- load_cfb_pbp(seasons = 2021)
# Convert to data frame
cfb_2021 <- as.data.frame(cfb_2021)

Play by Play

# Create summarized play type
s_play_type <- create_simplex_play_type(cfb_2021)
# Add summarized play type back to dataset
cfb_2021$playtype <- s_play_type

2021 Notre Dame Offensive Stats

nd_off_2021 <- cfb_2021[which(cfb_2021$pos_team == "Notre Dame" &
                     cfb_2021$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

nd_off_2021_m <- melt(nd_off_2021[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2020 ND Offense EPA

# Create plot
ggplot(nd_off_2021_m, # Set dataset
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set geom density ridges
  theme_minimal() + # Set theme as minimal
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
       subtitle = "2021 Notre Dame Offense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw()+ # Convert to dark theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.854
## Warning: Removed 3 rows containing non-finite values (`stat_density_ridges()`).

# Turn off dark mode
invert_geom_defaults()

2021 Notre Dame Defense

nd_def_2021 <- cfb_2021[which(cfb_2021$def_pos_team == "Notre Dame" &
                     cfb_2021$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

nd_def_2021_m <- melt(nd_def_2021[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2021 ND Defense EPA

# Create plot
ggplot(nd_def_2021_m, # Set dataset 
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set density to create distribution
  theme_minimal() + # Set plot theme
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
       subtitle = "2021 Notre Dame Defense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw() + # Set theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.61
## Warning: Removed 3 rows containing non-finite values (`stat_density_ridges()`).

# Turn off dark mode
invert_geom_defaults()

By Field Position

y1_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$yards_to_goal >= 80,], 
                                team_logos = team_logos)

# Calculate statistics for 20 - 50
y2_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$yards_to_goal >= 50 &
                                                 cfb_2021$yards_to_goal < 80,], 
                                team_logos = team_logos)

# Calculate statistics for 50 - 79
y3_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$yards_to_goal >= 21 &
                                                 cfb_2021$yards_to_goal < 50,], 
                                team_logos = team_logos)
# Calculate statistics for 80 - 100
y4_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$yards_to_goal >= 0 &
                                                 cfb_2021$yards_to_goal < 20,], 
                                team_logos = team_logos)

2021 Notre Dame Offense

# Set team as Notre Dame
team <- "Notre Dame"

# Extract Notre Dame statistics
nd_by_yards_2021 <- rbind.data.frame(y1_2021_stats[y1_2021_stats$school == team,],
                             y2_2021_stats[y2_2021_stats$school == team,],
                             y3_2021_stats[y3_2021_stats$school == team,],
                             y4_2021_stats[y4_2021_stats$school == team,])

# Create plot for offensive EPA
nd_epa_off_2021 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2021, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
nd_epa_off_2021

# Create plot for offensive yards
nd_yards_2021 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2021, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "off") # Set type of plot to generate
# Generate plot
nd_yards_2021

2021 Defensive Stats

# Create plot for defensive EPA
nd_epa_def_2021 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2021, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
nd_epa_def_2021

# Yards
# Create plot for defensive yards
nd_yards_2021_def <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2021, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "def") # Set type of plot to generate
# Generate plot
nd_yards_2021_def

2021 Michigan

2021 Michigan Offensive Stats

mich_off_2021 <- cfb_2021[which(cfb_2021$pos_team == "Michigan" &
                     cfb_2021$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

mich_off_2021_m <- melt(mich_off_2021[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2021 Michigan Offense EPA

# Create plot
ggplot(mich_off_2021_m, # Set dataset
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set geom density ridges
  theme_minimal() + # Set theme as minimal
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
       subtitle = "2021 Michigan Offense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw()+ # Convert to dark theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.547

# Turn off dark mode
invert_geom_defaults()

2021 Michigan Defense

mich_def_2021 <- cfb_2021[which(cfb_2021$def_pos_team == "Michigan" &
                     cfb_2021$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

mich_def_2021_m <- melt(mich_def_2021[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2021 Michigan Defense EPA

# Create plot
ggplot(mich_def_2021_m, # Set dataset 
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set density to create distribution
  theme_minimal() + # Set plot theme
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
       subtitle = "2021 Michigan Defense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw() + # Set theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.7

# Turn off dark mode
invert_geom_defaults()

By Field Position

2021 Michigan Offense

# Set team as Notre Dame
team <- "Michigan"

# Extract Notre Dame statistics
mich_by_yards_2021 <- rbind.data.frame(y1_2021_stats[y1_2021_stats$school == team,],
                             y2_2021_stats[y2_2021_stats$school == team,],
                             y3_2021_stats[y3_2021_stats$school == team,],
                             y4_2021_stats[y4_2021_stats$school == team,])

# Create plot for offensive EPA
mich_epa_off_2021 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2021, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
mich_epa_off_2021

# Create plot for offensive yards
mich_yards_2021 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2021, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "off") # Set type of plot to generate
# Generate plot
mich_yards_2021

2021 Michigan Defensive Stats

# Create plot for defensive EPA
mich_epa_def_2021 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2021, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
mich_epa_def_2021

# Yards
# Create plot for defensive yards
mich_yards_2021_def <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2021, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "def") # Set type of plot to generate
# Generate plot
mich_yards_2021_def

2021 Notre Dame vs Michigan Matchup

# Setting Data Up
# ND Pass and Rush Offense and Defense
nd_by_yards_2021$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
nd_by_yards_2021$zone_pass_num <- paste(nd_by_yards_2021$zone, ": ", nd_by_yards_2021$pass_n, sep = "")
nd_by_yards_2021$zone_rush_num <- paste(nd_by_yards_2021$zone, ": ", nd_by_yards_2021$rush_n, sep = "")
nd_by_yards_2021$zone_pass_num_def <- paste(nd_by_yards_2021$zone, ": ", nd_by_yards_2021$pass_n_def, sep = "")
nd_by_yards_2021$zone_rush_num_def <- paste(nd_by_yards_2021$zone, ": ", nd_by_yards_2021$rush_n_def, sep = "")

# Michigan Pass and Rush Offense and Defense
mich_by_yards_2021$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
mich_by_yards_2021$zone_pass_num <- paste(mich_by_yards_2021$zone, ": ", mich_by_yards_2021$pass_n, sep = "")
mich_by_yards_2021$zone_rush_num <- paste(mich_by_yards_2021$zone, ": ", mich_by_yards_2021$rush_n, sep = "")
mich_by_yards_2021$zone_pass_num_def <- paste(mich_by_yards_2021$zone, ": ", mich_by_yards_2021$pass_n_def, sep = "")
mich_by_yards_2021$zone_rush_num_def <- paste(mich_by_yards_2021$zone, ": ", mich_by_yards_2021$rush_n_def, sep = "")

# Create Different Combinations
nd_v_mich_pass_2021 <- cbind.data.frame(nd_by_yards_2021$zone, 
                                    ((nd_by_yards_2021$pass_wpa_per_play + mich_by_yards_2021$pass_wpa_per_play_def)/2),
                                    ((nd_by_yards_2021$pass_epa_per_play + mich_by_yards_2021$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2021) <- c("zone", "exp_wpa", "exp_epa")
nd_v_mich_rush_2021 <- cbind.data.frame(nd_by_yards_2021$zone, 
                                    ((nd_by_yards_2021$rush_wpa_per_play + mich_by_yards_2021$rush_wpa_per_play_def)/2),
                                    ((nd_by_yards_2021$rush_epa_per_play + mich_by_yards_2021$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2021) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_pass_2021 <- cbind.data.frame(mich_by_yards_2021$zone, 
                                    ((mich_by_yards_2021$pass_wpa_per_play + nd_by_yards_2021$pass_wpa_per_play_def)/2),
                                    ((mich_by_yards_2021$pass_epa_per_play + nd_by_yards_2021$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2021) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_rush_2021 <- cbind.data.frame(mich_by_yards_2021$zone, 
                                    ((mich_by_yards_2021$rush_wpa_per_play + nd_by_yards_2021$rush_wpa_per_play_def)/2),
                                    ((mich_by_yards_2021$rush_epa_per_play + nd_by_yards_2021$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2021) <- c("zone", "exp_wpa", "exp_epa")

WPA Graphs by Field Position

# ND Passing Offense vs Michigan Passing Defense
g_2021_1 <- ggplot(data = nd_v_mich_pass_2021, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2021_1

# ND Rushing Offense vs Michigan Rushing Defense
g_2021_2 <- ggplot(data = nd_v_mich_rush_2021, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2021_2

# Michigan Pass Offense vs Notre Dame Pass Defense
g_2021_3 <- ggplot(data = mich_v_nd_pass_2021, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2021_3

# Michigan Rush Offense vs Notre Dame Rush Defense
g_2021_4 <- ggplot(data = mich_v_nd_rush_2021, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2021_4

Michigan with an easy advantage here. Notre Dame passing offense is better, but rushing offense is non existent. Michigan offense in general can move on ND’s defense.

EPA Graphs by Field Position

# ND Passing Offense vs Michigan Passing Defense
g_2021_5 <- ggplot(data = nd_v_mich_pass_2021, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2021_5

# ND Rushing Offense vs Michigan Rushing Defense
g_2021_6 <- ggplot(data = nd_v_mich_rush_2021, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2021_6

# Michigan Pass Offense vs Notre Dame Pass Defense
g_2021_7 <- ggplot(data = mich_v_nd_pass_2021, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Expected Points Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2021_7

# Michigan Rush Offense vs Notre Dame Rush Defense
g_2021_8 <- ggplot(data = mich_v_nd_rush_2021, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2021_8

Paints a better picture for the Irish passing attack, but they would still be outclassed on the ground. Michigan rushing offense looking generally better compared to their passing attack, but a balanced approach would be encouraged.

Extracting Plays by Downs

z1_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$down == 1,], 
                                team_logos = team_logos)

# Calculate statistics for 2nd Down
z2_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$down == 2,], 
                                team_logos = team_logos)

# Calculate statistics for 3rd Down
z3_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$down == 3,], 
                                team_logos = team_logos)
# Calculate statistics for 4th Down
z4_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$down == 4,], 
                                team_logos = team_logos)

# Set team as Notre Dame
team <- "Notre Dame"

# Extract Notre Dame statistics
nd_by_down_2021 <- rbind.data.frame(z1_2021_stats[z1_2021_stats$school == team,],
                             z2_2021_stats[z2_2021_stats$school == team,],
                             z3_2021_stats[z3_2021_stats$school == team,],
                             z4_2021_stats[z4_2021_stats$school == team,])

# Set team as Michigan
team <- "Michigan"

# Extract Michigan statistics
mich_by_down_2021 <- rbind.data.frame(z1_2021_stats[z1_2021_stats$school == team,],
                             z2_2021_stats[z2_2021_stats$school == team,],
                             z3_2021_stats[z3_2021_stats$school == team,],
                             z4_2021_stats[z4_2021_stats$school == team,])

Setup Comparisons by Down

# ND Pass and Rush Offense and Defense
nd_by_down_2021$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
nd_by_down_2021$down_pass_num <- paste(nd_by_down_2021$down, ": ", nd_by_down_2021$pass_n, sep = "")
nd_by_down_2021$down_rush_num <- paste(nd_by_down_2021$down, ": ", nd_by_down_2021$rush_n, sep = "")
nd_by_down_2021$down_pass_num_def <- paste(nd_by_down_2021$down, ": ", nd_by_down_2021$pass_n_def, sep = "")
nd_by_down_2021$down_rush_num_def <- paste(nd_by_down_2021$down, ": ", nd_by_down_2021$rush_n_def, sep = "")

# Michigan Pass and Rush Offense and Defense
mich_by_down_2021$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
mich_by_down_2021$down_pass_num <- paste(mich_by_down_2021$down, ": ", mich_by_down_2021$pass_n, sep = "")
mich_by_down_2021$down_rush_num <- paste(mich_by_down_2021$down, ": ", mich_by_down_2021$rush_n, sep = "")
mich_by_down_2021$down_pass_num_def <- paste(mich_by_down_2021$down, ": ", mich_by_down_2021$pass_n_def, sep = "")
mich_by_down_2021$down_rush_num_def <- paste(mich_by_down_2021$down, ": ", mich_by_down_2021$rush_n_def, sep = "")

# Create Different Combinations
nd_v_mich_pass_2021_down <- cbind.data.frame(nd_by_down_2021$down, 
                                    ((nd_by_down_2021$pass_wpa_per_play + mich_by_down_2021$pass_wpa_per_play_def)/2),
                                    ((nd_by_down_2021$pass_epa_per_play + mich_by_down_2021$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2021_down) <- c("down", "exp_wpa", "exp_epa")
nd_v_mich_rush_2021_down <- cbind.data.frame(nd_by_down_2021$down, 
                                    ((nd_by_down_2021$rush_wpa_per_play + mich_by_down_2021$rush_wpa_per_play_def)/2),
                                    ((nd_by_down_2021$rush_epa_per_play + mich_by_down_2021$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2021_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_pass_2021_down <- cbind.data.frame(mich_by_down_2021$down, 
                                    ((mich_by_down_2021$pass_wpa_per_play + nd_by_down_2021$pass_wpa_per_play_def)/2),
                                    ((mich_by_down_2021$pass_epa_per_play + nd_by_down_2021$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2021_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_rush_2021_down <- cbind.data.frame(mich_by_down_2021$down, 
                                    ((mich_by_down_2021$rush_wpa_per_play + nd_by_down_2021$rush_wpa_per_play_def)/2),
                                    ((mich_by_down_2021$rush_epa_per_play + nd_by_down_2021$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2021_down) <- c("down", "exp_wpa", "exp_epa")

WPA Graphs by Down

# ND Passing Offense vs Michigan Passing Defense
d_2021_1 <- ggplot(data = nd_v_mich_pass_2021_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2021_1

# ND Rushing Offense vs Michigan Rushing Defense
d_2021_2 <- ggplot(data = nd_v_mich_rush_2021_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2021_2

# Michigan Pass Offense vs Notre Dame Pass Defense
d_2021_3 <- ggplot(data = mich_v_nd_pass_2021_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2021_3

# Michigan Rush Offense vs Notre Dame Rush Defense
d_2021_4 <- ggplot(data = mich_v_nd_rush_2021_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2021_4

Offenses have an advantage, but Michigan’s is performing better in both the pass and rush categories.

EPA by Down

# ND Passing Offense vs Michigan Passing Defense
d_2021_5 <- ggplot(data = nd_v_mich_pass_2021_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2021_5

# ND Rushing Offense vs Michigan Rushing Defense
d_2021_6 <- ggplot(data = nd_v_mich_rush_2021_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2021_6

# Michigan Pass Offense vs Notre Dame Pass Defense
d_2021_7 <- ggplot(data = mich_v_nd_pass_2021_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2021_7

# Michigan Rush Offense vs Notre Dame Rush Defense
d_2021_8 <- ggplot(data = mich_v_nd_rush_2021_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2021 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2021_8

Notre Dame would only fare decently by passing the ball, Michigan would be best taking a balanced approach.

2022 Season

Load 2022 Season

# Load data
cfb_2022 <- load_cfb_pbp(seasons = 2022)
# Convert to data frame
cfb_2022 <- as.data.frame(cfb_2022)

Play by Play

# Create summarized play type
s_play_type <- create_simplex_play_type(cfb_2022)
# Add summarized play type back to dataset
cfb_2022$playtype <- s_play_type

2022 Notre Dame Offensive Stats

nd_off_2022 <- cfb_2022[which(cfb_2022$pos_team == "Notre Dame" &
                     cfb_2022$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

nd_off_2022_m <- melt(nd_off_2022[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2022 ND Offense EPA

# Create plot
ggplot(nd_off_2022_m, # Set dataset
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set geom density ridges
  theme_minimal() + # Set theme as minimal
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
       subtitle = "2022 Notre Dame Offense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw()+ # Convert to dark theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.856

# Turn off dark mode
invert_geom_defaults()

2022 Notre Dame Defense

nd_def_2022 <- cfb_2022[which(cfb_2022$def_pos_team == "Notre Dame" &
                     cfb_2022$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

nd_def_2022_m <- melt(nd_def_2022[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2022 ND Defense EPA

# Create plot
ggplot(nd_def_2022_m, # Set dataset 
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set density to create distribution
  theme_minimal() + # Set plot theme
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
       subtitle = "2022 Notre Dame Defense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw() + # Set theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.8

# Turn off dark mode
invert_geom_defaults()

By Field Position

y1_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$yards_to_goal >= 80,], 
                                team_logos = team_logos)

# Calculate statistics for 20 - 50
y2_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$yards_to_goal >= 50 &
                                                 cfb_2022$yards_to_goal < 80,], 
                                team_logos = team_logos)

# Calculate statistics for 50 - 79
y3_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$yards_to_goal >= 21 &
                                                 cfb_2022$yards_to_goal < 50,], 
                                team_logos = team_logos)
# Calculate statistics for 80 - 100
y4_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$yards_to_goal >= 0 &
                                                 cfb_2022$yards_to_goal < 20,], 
                                team_logos = team_logos)

2022 Notre Dame Offense

# Set team as Notre Dame
team <- "Notre Dame"

# Extract Notre Dame statistics
nd_by_yards_2022 <- rbind.data.frame(y1_2022_stats[y1_2022_stats$school == team,],
                             y2_2022_stats[y2_2022_stats$school == team,],
                             y3_2022_stats[y3_2022_stats$school == team,],
                             y4_2022_stats[y4_2022_stats$school == team,])

# Create plot for offensive EPA
nd_epa_off_2022 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2022, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
nd_epa_off_2022

# Create plot for offensive yards
nd_yards_2022 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2022, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "off") # Set type of plot to generate
# Generate plot
nd_yards_2022

2022 Defensive Stats

# Create plot for defensive EPA
nd_epa_def_2022 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2022, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
nd_epa_def_2022

# Yards
# Create plot for defensive yards
nd_yards_2022_def <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2022, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "def") # Set type of plot to generate
# Generate plot
nd_yards_2022_def

2022 Michigan

2022 Michigan Offensive Stats

mich_off_2022 <- cfb_2022[which(cfb_2022$pos_team == "Michigan" &
                     cfb_2022$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

mich_off_2022_m <- melt(mich_off_2022[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2022 Michigan Offense EPA

# Create plot
ggplot(mich_off_2022_m, # Set dataset
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set geom density ridges
  theme_minimal() + # Set theme as minimal
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
       subtitle = "2022 Michigan Offense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw()+ # Convert to dark theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.556

# Turn off dark mode
invert_geom_defaults()

2022 Michigan Defense

mich_def_2022 <- cfb_2022[which(cfb_2022$def_pos_team == "Michigan" &
                     cfb_2022$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

mich_def_2022_m <- melt(mich_def_2022[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2022 Michigan Defense EPA

# Create plot
ggplot(mich_def_2022_m, # Set dataset 
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set density to create distribution
  theme_minimal() + # Set plot theme
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
       subtitle = "2022 Michigan Defense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw() + # Set theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.698

# Turn off dark mode
invert_geom_defaults()

By Field Position

2022 Michigan Offense

# Set team as Notre Dame
team <- "Michigan"

# Extract Notre Dame statistics
mich_by_yards_2022 <- rbind.data.frame(y1_2022_stats[y1_2022_stats$school == team,],
                             y2_2022_stats[y2_2022_stats$school == team,],
                             y3_2022_stats[y3_2022_stats$school == team,],
                             y4_2022_stats[y4_2022_stats$school == team,])

# Create plot for offensive EPA
mich_epa_off_2022 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2022, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
mich_epa_off_2022

# Create plot for offensive yards
mich_yards_2022 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2022, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "off") # Set type of plot to generate
# Generate plot
mich_yards_2022

2022 Michigan Defensive Stats

# Create plot for defensive EPA
mich_epa_def_2022 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2022, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
mich_epa_def_2022

# Yards
# Create plot for defensive yards
mich_yards_2022_def <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2022, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "def") # Set type of plot to generate
# Generate plot
mich_yards_2022_def

2022 Notre Dame vs Michigan Matchup

# Setting Data Up
# ND Pass and Rush Offense and Defense
nd_by_yards_2022$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
nd_by_yards_2022$zone_pass_num <- paste(nd_by_yards_2022$zone, ": ", nd_by_yards_2022$pass_n, sep = "")
nd_by_yards_2022$zone_rush_num <- paste(nd_by_yards_2022$zone, ": ", nd_by_yards_2022$rush_n, sep = "")
nd_by_yards_2022$zone_pass_num_def <- paste(nd_by_yards_2022$zone, ": ", nd_by_yards_2022$pass_n_def, sep = "")
nd_by_yards_2022$zone_rush_num_def <- paste(nd_by_yards_2022$zone, ": ", nd_by_yards_2022$rush_n_def, sep = "")

# Michigan Pass and Rush Offense and Defense
mich_by_yards_2022$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
mich_by_yards_2022$zone_pass_num <- paste(mich_by_yards_2022$zone, ": ", mich_by_yards_2022$pass_n, sep = "")
mich_by_yards_2022$zone_rush_num <- paste(mich_by_yards_2022$zone, ": ", mich_by_yards_2022$rush_n, sep = "")
mich_by_yards_2022$zone_pass_num_def <- paste(mich_by_yards_2022$zone, ": ", mich_by_yards_2022$pass_n_def, sep = "")
mich_by_yards_2022$zone_rush_num_def <- paste(mich_by_yards_2022$zone, ": ", mich_by_yards_2022$rush_n_def, sep = "")

# Create Different Combinations
nd_v_mich_pass_2022 <- cbind.data.frame(nd_by_yards_2022$zone, 
                                    ((nd_by_yards_2022$pass_wpa_per_play + mich_by_yards_2022$pass_wpa_per_play_def)/2),
                                    ((nd_by_yards_2022$pass_epa_per_play + mich_by_yards_2022$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2022) <- c("zone", "exp_wpa", "exp_epa")
nd_v_mich_rush_2022 <- cbind.data.frame(nd_by_yards_2022$zone, 
                                    ((nd_by_yards_2022$rush_wpa_per_play + mich_by_yards_2022$rush_wpa_per_play_def)/2),
                                    ((nd_by_yards_2022$rush_epa_per_play + mich_by_yards_2022$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2022) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_pass_2022 <- cbind.data.frame(mich_by_yards_2022$zone, 
                                    ((mich_by_yards_2022$pass_wpa_per_play + nd_by_yards_2022$pass_wpa_per_play_def)/2),
                                    ((mich_by_yards_2022$pass_epa_per_play + nd_by_yards_2022$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2022) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_rush_2022 <- cbind.data.frame(mich_by_yards_2022$zone, 
                                    ((mich_by_yards_2022$rush_wpa_per_play + nd_by_yards_2022$rush_wpa_per_play_def)/2),
                                    ((mich_by_yards_2022$rush_epa_per_play + nd_by_yards_2022$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2022) <- c("zone", "exp_wpa", "exp_epa")

WPA Graphs by Field Position

# ND Passing Offense vs Michigan Passing Defense
g_2022_1 <- ggplot(data = nd_v_mich_pass_2022, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2022_1

# ND Rushing Offense vs Michigan Rushing Defense
g_2022_2 <- ggplot(data = nd_v_mich_rush_2022, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2022_2

# Michigan Pass Offense vs Notre Dame Pass Defense
g_2022_3 <- ggplot(data = mich_v_nd_pass_2022, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2022_3

# Michigan Rush Offense vs Notre Dame Rush Defense
g_2022_4 <- ggplot(data = mich_v_nd_rush_2022, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2022_4

Notre Dame would once again be better served to go with the pass. Michigan would as well; Wolverines generally have an edge across the board.

EPA Graphs by Field Position

# ND Passing Offense vs Michigan Passing Defense
g_2022_5 <- ggplot(data = nd_v_mich_pass_2022, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2022_5

# ND Rushing Offense vs Michigan Rushing Defense
g_2022_6 <- ggplot(data = nd_v_mich_rush_2022, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2022_6

# Michigan Pass Offense vs Notre Dame Pass Defense
g_2022_7 <- ggplot(data = mich_v_nd_pass_2022, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Expected Points Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2022_7

# Michigan Rush Offense vs Notre Dame Rush Defense
g_2022_8 <- ggplot(data = mich_v_nd_rush_2022, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2022_8

Notre Dame would be better to pass the ball, while Michigan would use a more balanced attack.

Extracting Plays by Downs

z1_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$down == 1,], 
                                team_logos = team_logos)

# Calculate statistics for 2nd Down
z2_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$down == 2,], 
                                team_logos = team_logos)

# Calculate statistics for 3rd Down
z3_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$down == 3,], 
                                team_logos = team_logos)
# Calculate statistics for 4th Down
z4_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$down == 4,], 
                                team_logos = team_logos)

# Set team as Notre Dame
team <- "Notre Dame"

# Extract Notre Dame statistics
nd_by_down_2022 <- rbind.data.frame(z1_2022_stats[z1_2022_stats$school == team,],
                             z2_2022_stats[z2_2022_stats$school == team,],
                             z3_2022_stats[z3_2022_stats$school == team,],
                             z4_2022_stats[z4_2022_stats$school == team,])

# Set team as Michigan
team <- "Michigan"

# Extract Michigan statistics
mich_by_down_2022 <- rbind.data.frame(z1_2022_stats[z1_2022_stats$school == team,],
                             z2_2022_stats[z2_2022_stats$school == team,],
                             z3_2022_stats[z3_2022_stats$school == team,],
                             z4_2022_stats[z4_2022_stats$school == team,])

Setup Comparisons by Down

# ND Pass and Rush Offense and Defense
nd_by_down_2022$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
nd_by_down_2022$down_pass_num <- paste(nd_by_down_2022$down, ": ", nd_by_down_2022$pass_n, sep = "")
nd_by_down_2022$down_rush_num <- paste(nd_by_down_2022$down, ": ", nd_by_down_2022$rush_n, sep = "")
nd_by_down_2022$down_pass_num_def <- paste(nd_by_down_2022$down, ": ", nd_by_down_2022$pass_n_def, sep = "")
nd_by_down_2022$down_rush_num_def <- paste(nd_by_down_2022$down, ": ", nd_by_down_2022$rush_n_def, sep = "")

# Michigan Pass and Rush Offense and Defense
mich_by_down_2022$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
mich_by_down_2022$down_pass_num <- paste(mich_by_down_2022$down, ": ", mich_by_down_2022$pass_n, sep = "")
mich_by_down_2022$down_rush_num <- paste(mich_by_down_2022$down, ": ", mich_by_down_2022$rush_n, sep = "")
mich_by_down_2022$down_pass_num_def <- paste(mich_by_down_2022$down, ": ", mich_by_down_2022$pass_n_def, sep = "")
mich_by_down_2022$down_rush_num_def <- paste(mich_by_down_2022$down, ": ", mich_by_down_2022$rush_n_def, sep = "")

# Create Different Combinations
nd_v_mich_pass_2022_down <- cbind.data.frame(nd_by_down_2022$down, 
                                    ((nd_by_down_2022$pass_wpa_per_play + mich_by_down_2022$pass_wpa_per_play_def)/2),
                                    ((nd_by_down_2022$pass_epa_per_play + mich_by_down_2022$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2022_down) <- c("down", "exp_wpa", "exp_epa")
nd_v_mich_rush_2022_down <- cbind.data.frame(nd_by_down_2022$down, 
                                    ((nd_by_down_2022$rush_wpa_per_play + mich_by_down_2022$rush_wpa_per_play_def)/2),
                                    ((nd_by_down_2022$rush_epa_per_play + mich_by_down_2022$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2022_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_pass_2022_down <- cbind.data.frame(mich_by_down_2022$down, 
                                    ((mich_by_down_2022$pass_wpa_per_play + nd_by_down_2022$pass_wpa_per_play_def)/2),
                                    ((mich_by_down_2022$pass_epa_per_play + nd_by_down_2022$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2022_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_rush_2022_down <- cbind.data.frame(mich_by_down_2022$down, 
                                    ((mich_by_down_2022$rush_wpa_per_play + nd_by_down_2022$rush_wpa_per_play_def)/2),
                                    ((mich_by_down_2022$rush_epa_per_play + nd_by_down_2022$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2022_down) <- c("down", "exp_wpa", "exp_epa")

WPA Graphs by Down

# ND Passing Offense vs Michigan Passing Defense
d_2022_1 <- ggplot(data = nd_v_mich_pass_2022_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2022_1

# ND Rushing Offense vs Michigan Rushing Defense
d_2022_2 <- ggplot(data = nd_v_mich_rush_2022_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2022_2

# Michigan Pass Offense vs Notre Dame Pass Defense
d_2022_3 <- ggplot(data = mich_v_nd_pass_2022_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2022_3

# Michigan Rush Offense vs Notre Dame Rush Defense
d_2022_4 <- ggplot(data = mich_v_nd_rush_2022_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2022_4

Irish - pass. Wolverines - balanced attack.

EPA by Down

# ND Passing Offense vs Michigan Passing Defense
d_2022_5 <- ggplot(data = nd_v_mich_pass_2022_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2022_5

# ND Rushing Offense vs Michigan Rushing Defense
d_2022_6 <- ggplot(data = nd_v_mich_rush_2022_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2022_6

# Michigan Pass Offense vs Notre Dame Pass Defense
d_2022_7 <- ggplot(data = mich_v_nd_pass_2022_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2022_7

# Michigan Rush Offense vs Notre Dame Rush Defense
d_2022_8 <- ggplot(data = mich_v_nd_rush_2022_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2022 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2022_8

Same as other analyses, but gives more favorability to Michigan’s passing attack.

2023 Season

Load 2023 Season

# Load data
cfb_2023 <- load_cfb_pbp(seasons = 2023)
# Convert to data frame
cfb_2023 <- as.data.frame(cfb_2023)

Play by Play

# Create summarized play type
s_play_type <- create_simplex_play_type(cfb_2023)
# Add summarized play type back to dataset
cfb_2023$playtype <- s_play_type

2023 Notre Dame Offensive Stats

nd_off_2023 <- cfb_2023[which(cfb_2023$pos_team == "Notre Dame" &
                     cfb_2023$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

nd_off_2023_m <- melt(nd_off_2023[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2023 ND Offense EPA

# Create plot
ggplot(nd_off_2023_m, # Set dataset
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set geom density ridges
  theme_minimal() + # Set theme as minimal
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
       subtitle = "2023 Notre Dame Offense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw()+ # Convert to dark theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.795

# Turn off dark mode
invert_geom_defaults()

2023 Notre Dame Defense

nd_def_2023 <- cfb_2023[which(cfb_2023$def_pos_team == "Notre Dame" &
                     cfb_2023$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

nd_def_2023_m <- melt(nd_def_2023[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2023 ND Defense EPA

# Create plot
ggplot(nd_def_2023_m, # Set dataset 
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set density to create distribution
  theme_minimal() + # Set plot theme
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
       subtitle = "2023 Notre Dame Defense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw() + # Set theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.625

# Turn off dark mode
invert_geom_defaults()

By Field Position

y1_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$yards_to_goal >= 80,], 
                                team_logos = team_logos)

# Calculate statistics for 20 - 50
y2_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$yards_to_goal >= 50 &
                                                 cfb_2023$yards_to_goal < 80,], 
                                team_logos = team_logos)

# Calculate statistics for 50 - 79
y3_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$yards_to_goal >= 21 &
                                                 cfb_2023$yards_to_goal < 50,], 
                                team_logos = team_logos)
# Calculate statistics for 80 - 100
y4_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$yards_to_goal >= 0 &
                                                 cfb_2023$yards_to_goal < 20,], 
                                team_logos = team_logos)

2023 Notre Dame Offense

# Set team as Notre Dame
team <- "Notre Dame"

# Extract Notre Dame statistics
nd_by_yards_2023 <- rbind.data.frame(y1_2023_stats[y1_2023_stats$school == team,],
                             y2_2023_stats[y2_2023_stats$school == team,],
                             y3_2023_stats[y3_2023_stats$school == team,],
                             y4_2023_stats[y4_2023_stats$school == team,])

# Create plot for offensive EPA
nd_epa_off_2023 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2023, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
nd_epa_off_2023

# Create plot for offensive yards
nd_yards_2023 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2023, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "off") # Set type of plot to generate
# Generate plot
nd_yards_2023

2023 Defensive Stats

# Create plot for defensive EPA
nd_epa_def_2023 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2023, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
nd_epa_def_2023

# Yards
# Create plot for defensive yards
nd_yards_2023_def <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Notre Dame", # Set  Team
                 res_1 = nd_by_yards_2023, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "def") # Set type of plot to generate
# Generate plot
nd_yards_2023_def

2023 Michigan

2023 Michigan Offensive Stats

mich_off_2023 <- cfb_2023[which(cfb_2023$pos_team == "Michigan" &
                     cfb_2023$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

mich_off_2023_m <- melt(mich_off_2023[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2023 Michigan Offense EPA

# Create plot
ggplot(mich_off_2023_m, # Set dataset
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set geom density ridges
  theme_minimal() + # Set theme as minimal
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
       subtitle = "2023 Michigan Offense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw()+ # Convert to dark theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.468

# Turn off dark mode
invert_geom_defaults()

2023 Michigan Defense

mich_def_2023 <- cfb_2023[which(cfb_2023$def_pos_team == "Michigan" &
                     cfb_2023$playtype %in% c("Rush", "Pass")),
                       c("down", "playtype", "wpa", "EPA")]

mich_def_2023_m <- melt(mich_def_2023[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))

2023 Michigan Defense EPA

# Create plot
ggplot(mich_def_2023_m, # Set dataset 
       aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
  geom_density_ridges(alpha = 0.5) + # Set density to create distribution
  theme_minimal() + # Set plot theme
  labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
       subtitle = "2023 Michigan Defense",
       fill = "Play Type", y = "Down") +
  scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
  dark_theme_bw() + # Set theme
  geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.637

# Turn off dark mode
invert_geom_defaults()

By Field Position

2023 Michigan Offense

# Set team as Notre Dame
team <- "Michigan"

# Extract Notre Dame statistics
mich_by_yards_2023 <- rbind.data.frame(y1_2023_stats[y1_2023_stats$school == team,],
                             y2_2023_stats[y2_2023_stats$school == team,],
                             y3_2023_stats[y3_2023_stats$school == team,],
                             y4_2023_stats[y4_2023_stats$school == team,])

# Create plot for offensive EPA
mich_epa_off_2023 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2023, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
mich_epa_off_2023

# Create plot for offensive yards
mich_yards_2023 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2023, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "off") # Set type of plot to generate
# Generate plot
mich_yards_2023

2023 Michigan Defensive Stats

# Create plot for defensive EPA
mich_epa_def_2023 <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2023, # Set  Team Stats
                 type = "EPA", # Set type as EPA
                 off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
mich_epa_def_2023

# Yards
# Create plot for defensive yards
mich_yards_2023_def <- create_zone_plot(logos = team_logos, # Set team logos
                 t1 = "Michigan", # Set  Team
                 res_1 = mich_by_yards_2023, # Set  Team Stats
                 type = "Yards", # Set type as Yards
                 off_def = "def") # Set type of plot to generate
# Generate plot
mich_yards_2023_def

2023 Notre Dame vs Michigan Matchup

# Setting Data Up
# ND Pass and Rush Offense and Defense
nd_by_yards_2023$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
nd_by_yards_2023$zone_pass_num <- paste(nd_by_yards_2023$zone, ": ", nd_by_yards_2023$pass_n, sep = "")
nd_by_yards_2023$zone_rush_num <- paste(nd_by_yards_2023$zone, ": ", nd_by_yards_2023$rush_n, sep = "")
nd_by_yards_2023$zone_pass_num_def <- paste(nd_by_yards_2023$zone, ": ", nd_by_yards_2023$pass_n_def, sep = "")
nd_by_yards_2023$zone_rush_num_def <- paste(nd_by_yards_2023$zone, ": ", nd_by_yards_2023$rush_n_def, sep = "")

# Michigan Pass and Rush Offense and Defense
mich_by_yards_2023$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
mich_by_yards_2023$zone_pass_num <- paste(mich_by_yards_2023$zone, ": ", mich_by_yards_2023$pass_n, sep = "")
mich_by_yards_2023$zone_rush_num <- paste(mich_by_yards_2023$zone, ": ", mich_by_yards_2023$rush_n, sep = "")
mich_by_yards_2023$zone_pass_num_def <- paste(mich_by_yards_2023$zone, ": ", mich_by_yards_2023$pass_n_def, sep = "")
mich_by_yards_2023$zone_rush_num_def <- paste(mich_by_yards_2023$zone, ": ", mich_by_yards_2023$rush_n_def, sep = "")

# Create Different Combinations
nd_v_mich_pass_2023 <- cbind.data.frame(nd_by_yards_2023$zone, 
                                    ((nd_by_yards_2023$pass_wpa_per_play + mich_by_yards_2023$pass_wpa_per_play_def)/2),
                                    ((nd_by_yards_2023$pass_epa_per_play + mich_by_yards_2023$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2023) <- c("zone", "exp_wpa", "exp_epa")
nd_v_mich_rush_2023 <- cbind.data.frame(nd_by_yards_2023$zone, 
                                    ((nd_by_yards_2023$rush_wpa_per_play + mich_by_yards_2023$rush_wpa_per_play_def)/2),
                                    ((nd_by_yards_2023$rush_epa_per_play + mich_by_yards_2023$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2023) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_pass_2023 <- cbind.data.frame(mich_by_yards_2023$zone, 
                                    ((mich_by_yards_2023$pass_wpa_per_play + nd_by_yards_2023$pass_wpa_per_play_def)/2),
                                    ((mich_by_yards_2023$pass_epa_per_play + nd_by_yards_2023$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2023) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_rush_2023 <- cbind.data.frame(mich_by_yards_2023$zone, 
                                    ((mich_by_yards_2023$rush_wpa_per_play + nd_by_yards_2023$rush_wpa_per_play_def)/2),
                                    ((mich_by_yards_2023$rush_epa_per_play + nd_by_yards_2023$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2023) <- c("zone", "exp_wpa", "exp_epa")

WPA Graphs by Field Position

# ND Passing Offense vs Michigan Passing Defense
g_2023_1 <- ggplot(data = nd_v_mich_pass_2023, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2023_1

# ND Rushing Offense vs Michigan Rushing Defense
g_2023_2 <- ggplot(data = nd_v_mich_rush_2023, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2023_2

# Michigan Pass Offense vs Notre Dame Pass Defense
g_2023_3 <- ggplot(data = mich_v_nd_pass_2023, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2023_3

# Michigan Rush Offense vs Notre Dame Rush Defense
g_2023_4 <- ggplot(data = mich_v_nd_rush_2023, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2023_4

Both teams would be much better served to throw the ball primarily.

EPA Graphs by Field Position

# ND Passing Offense vs Michigan Passing Defense
g_2023_5 <- ggplot(data = nd_v_mich_pass_2023, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2023_5

# ND Rushing Offense vs Michigan Rushing Defense
g_2023_6 <- ggplot(data = nd_v_mich_rush_2023, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2023_6

# Michigan Pass Offense vs Notre Dame Pass Defense
g_2023_7 <- ggplot(data = mich_v_nd_pass_2023, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Win Expected Points Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2023_7

# Michigan Rush Offense vs Notre Dame Rush Defense
g_2023_8 <- ggplot(data = mich_v_nd_rush_2023, # Set dataset
              aes(x = zone,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = zone)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Field Zones", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
g_2023_8

Both teams would be much better positioned to throw the ball compared to running it.

Extracting Plays by Downs

z1_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$down == 1,], 
                                team_logos = team_logos)

# Calculate statistics for 2nd Down
z2_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$down == 2,], 
                                team_logos = team_logos)

# Calculate statistics for 3rd Down
z3_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$down == 3,], 
                                team_logos = team_logos)
# Calculate statistics for 4th Down
z4_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$down == 4,], 
                                team_logos = team_logos)

# Set team as Notre Dame
team <- "Notre Dame"

# Extract Notre Dame statistics
nd_by_down_2023 <- rbind.data.frame(z1_2023_stats[z1_2023_stats$school == team,],
                             z2_2023_stats[z2_2023_stats$school == team,],
                             z3_2023_stats[z3_2023_stats$school == team,],
                             z4_2023_stats[z4_2023_stats$school == team,])

# Set team as Michigan
team <- "Michigan"

# Extract Michigan statistics
mich_by_down_2023 <- rbind.data.frame(z1_2023_stats[z1_2023_stats$school == team,],
                             z2_2023_stats[z2_2023_stats$school == team,],
                             z3_2023_stats[z3_2023_stats$school == team,],
                             z4_2023_stats[z4_2023_stats$school == team,])

Setup Comparisons by Down

# ND Pass and Rush Offense and Defense
nd_by_down_2023$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
nd_by_down_2023$down_pass_num <- paste(nd_by_down_2023$down, ": ", nd_by_down_2023$pass_n, sep = "")
nd_by_down_2023$down_rush_num <- paste(nd_by_down_2023$down, ": ", nd_by_down_2023$rush_n, sep = "")
nd_by_down_2023$down_pass_num_def <- paste(nd_by_down_2023$down, ": ", nd_by_down_2023$pass_n_def, sep = "")
nd_by_down_2023$down_rush_num_def <- paste(nd_by_down_2023$down, ": ", nd_by_down_2023$rush_n_def, sep = "")

# Michigan Pass and Rush Offense and Defense
mich_by_down_2023$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
mich_by_down_2023$down_pass_num <- paste(mich_by_down_2023$down, ": ", mich_by_down_2023$pass_n, sep = "")
mich_by_down_2023$down_rush_num <- paste(mich_by_down_2023$down, ": ", mich_by_down_2023$rush_n, sep = "")
mich_by_down_2023$down_pass_num_def <- paste(mich_by_down_2023$down, ": ", mich_by_down_2023$pass_n_def, sep = "")
mich_by_down_2023$down_rush_num_def <- paste(mich_by_down_2023$down, ": ", mich_by_down_2023$rush_n_def, sep = "")

# Create Different Combinations
nd_v_mich_pass_2023_down <- cbind.data.frame(nd_by_down_2023$down, 
                                    ((nd_by_down_2023$pass_wpa_per_play + mich_by_down_2023$pass_wpa_per_play_def)/2),
                                    ((nd_by_down_2023$pass_epa_per_play + mich_by_down_2023$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2023_down) <- c("down", "exp_wpa", "exp_epa")
nd_v_mich_rush_2023_down <- cbind.data.frame(nd_by_down_2023$down, 
                                    ((nd_by_down_2023$rush_wpa_per_play + mich_by_down_2023$rush_wpa_per_play_def)/2),
                                    ((nd_by_down_2023$rush_epa_per_play + mich_by_down_2023$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2023_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_pass_2023_down <- cbind.data.frame(mich_by_down_2023$down, 
                                    ((mich_by_down_2023$pass_wpa_per_play + nd_by_down_2023$pass_wpa_per_play_def)/2),
                                    ((mich_by_down_2023$pass_epa_per_play + nd_by_down_2023$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2023_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_rush_2023_down <- cbind.data.frame(mich_by_down_2023$down, 
                                    ((mich_by_down_2023$rush_wpa_per_play + nd_by_down_2023$rush_wpa_per_play_def)/2),
                                    ((mich_by_down_2023$rush_epa_per_play + nd_by_down_2023$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2023_down) <- c("down", "exp_wpa", "exp_epa")

WPA Graphs by Down

# ND Passing Offense vs Michigan Passing Defense
d_2023_1 <- ggplot(data = nd_v_mich_pass_2023_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2023_1

# ND Rushing Offense vs Michigan Rushing Defense
d_2023_2 <- ggplot(data = nd_v_mich_rush_2023_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2023_2

# Michigan Pass Offense vs Notre Dame Pass Defense
d_2023_3 <- ggplot(data = mich_v_nd_pass_2023_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2023_3

# Michigan Rush Offense vs Notre Dame Rush Defense
d_2023_4 <- ggplot(data = mich_v_nd_rush_2023_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_wpa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2023_4

Both teams would be better served to throw the ball compared to running it.

EPA by Down

# ND Passing Offense vs Michigan Passing Defense
d_2023_5 <- ggplot(data = nd_v_mich_pass_2023_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
       title = "Notre Dame Passing Offense vs Michigan Pass Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2023_5

# ND Rushing Offense vs Michigan Rushing Defense
d_2023_6 <- ggplot(data = nd_v_mich_rush_2023_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
       title = "Notre Dame Rush Offense vs Michigan Rush Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2023_6

# Michigan Pass Offense vs Notre Dame Pass Defense
d_2023_7 <- ggplot(data = mich_v_nd_pass_2023_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Pass",
       title = "Michigan Passing Offense vs Notre Dame Pass Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2023_7

# Michigan Rush Offense vs Notre Dame Rush Defense
d_2023_8 <- ggplot(data = mich_v_nd_rush_2023_down, # Set dataset
              aes(x = down,  # Set x axis as pitch type
                  y= exp_epa, # Set y as win probability change
                  fill = down)) + # Set fill as pitch type
  geom_bar(stat = "identity") + # Set geom bar for bar plot
  theme_bw() + # Set theme 
  coord_flip() + # Flip coordinates
  labs(x = "Down", # Set labels
       y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
       title = "Michigan Rush Offense vs Notre Dame Rush Defense",
       subtitle = "2023 Season") +
  theme(panel.grid.major = element_blank(), # Remove grid
        panel.grid.minor = element_blank(), # Remove grid
        panel.border = element_blank(), # Remove grid
        panel.background = element_blank()) + # Remove grid
  guides(fill = NULL) # Turn off legend for fill
d_2023_8

Both teams would be much better served to throw the ball compared to running it.